Comentários
A linguagem Java utiliza pacotes para fazer a organização das classes e recursos.
Fonte: Shutterstock.
Deseja ouvir este material?
Áudio disponível no material digital.
Prezado aluno, bem-vindo a mais uma seção do livro Linguagem Orientada a Objetos. Nesta seção, alguns dos conceitos fundamentais da Orientação a Objetos (OO) serão apresentados. Qual programador não gosta de desenvolver códigos reutilizáveis, bem organizados e que possam ser facilmente utilizados por outros programadores? A maioria dos desenvolvedores, se não todos, gosta desses aspectos em seus sistemas, mas, afinal, como construir softwares reutilizáveis, como organizar melhor a aplicação Java e como incorporar bibliotecas desenvolvidas por outros desenvolvedores? Nesta seção, você terá a oportunidade de responder a essas e outras perguntas.
De forma a contextualizar sua aprendizagem, lembre-se de que você trabalha em uma startup e que seu chefe quer desenvolver um simulador completo de robô em Java que seja capaz de transportar caixas em uma sala. Para tanto, mais uma vez, o seu chefe se reuniu com você e pediu para que organizasse melhor o seu projeto, utilizando a estrutura de pacotes que o Java fornece. Ele analisou as classes que você implementou e percebeu que, praticamente, nenhum modificador de acesso foi utilizado. Dessa maneira, ele pediu a você que colocasse modificadores de acessos adequados aos atributos e métodos, e ao analisar as classes, percebeu que os atributos estavam sendo acessados diretamente, logo, solicitou que criasse métodos getters e setters onde fossem necessários, de forma a encapsular melhor os atributos. Ele também analisou as suas classes e percebeu que você não utilizou herança em nenhum lugar, solicitando a você que começasse a utilizar mais esse conceito em sua aplicação, bem como começasse a sobrescrever os métodos toString e equals e continuasse a utilizar GitHub e a estudar alguns clientes GUI para o GitHub, de forma a melhorar a utilização desse gerenciador de repositório.
Diante do desafio que lhe foi apresentado, como você irá organizar o seu código? O que são pacotes? O que são modificadores de acesso? Como você definirá os modificadores para os seus atributos? O que são métodos getters e setters e como criá-los? O que é herança e como você irá utilizá-la? O que são clientes GUI para GitHub?
Acalme-se. Esta seção irá ajudá-lo a ter um maior domínio dos conteúdos mencionados.
Agora, uma vez que foi apresentado à sua nova situação-problema, estude esta seção e compreenda os três grandes pilares da orientação a objetos, que são: encasulamento, herança e polimorfismo. Esses conceitos somados aos conceitos de classe, objeto, método e atributo englobam as ideias centrais da OO. E aí, vamos juntos compreender esses conceitos e resolver esse desafio?
Bom estudo!
As linguagens Orientadas a Objetos (OO), como Java, possuem três importantes pilares, que são: o encapsulamento, a herança e o polimorfismo, e outro conceito presente na linguagem Java diz respeito aos modificadores de acesso. Esses são alguns dos conceitos abordados em mais detalhes nesta seção, porém, antes de darmos continuidade, gostaríamos de ressaltar um ponto importante. Alguns dos exemplos apresentados nesta seção são apenas fragmentos de código. Dessa maneira, é importante que o aluno implemente o restante da aplicação, por exemplo, criando a classe e o método main.
Antes de avançarmos nesta seção, você deve ter em mente que a linguagem Java tem a capacidade de desenvolver aplicações que podem crescer bastante. Até este momento, desenvolvemos aplicações com poucas classes, mas imagine que você está desenvolvendo uma aplicação que está crescendo e que conta com 10 classes, logo, começa a ficar incomodado com a organização. No entanto, você ainda não faz nada e ela continua a crescer, agora, ela tem 20 classes. Nesse ponto, você pensa que não dá mais para continuar a desenvolver sem antes organizar as classes em estruturas que simplifiquem o processo.
A fim de entendermos como é desenvolvido um grande projeto em Java, em primeiro lugar, vamos apresentar uma visão geral de como é feita a organização de um projeto qualquer. Sendo assim, a Figura 2.7 mostra a organização da estrutura de um projeto qualquer desenvolvido em Java.
De acordo com a Figura 2.7, o projeto é subdividido em algumas partes. Inicialmente, temos o nome do projeto e, em seguida, temos o local em que ficam armazenados os códigos-fonte desenvolvidos pelo usuário. Conforme o código desenvolvido cresce, faz-se necessário criar uma estrutura para organizar as classes. Chamamos essa estrutura organizacional de pacote. Nesse exemplo, temos os seguintes pacotes: core, struct, util e Windows dentro do pacote code, os pacotes img e audio dentro do pacote resources e, por fim, temos o pacote icon dentro do pacote img. O ideal é que cada pacote contenha classes relacionadas ao nome do pacote. Nesse exemplo, dentro do pacote code.struct temos três classes, que são: Data, Parse e Point. Em seguida, temos o local em que ficam armazenadas as bibliotecas, e nesse local, existem dois tipos de bibliotecas, que são as bibliotecas padrão e as bibliotecas não padrão do Java. No exemplo acima, a biblioteca não padrão, chamada jdom.jar, foi importada para utilização e o conjunto de bibliotecas padrão do JDK também foi importado, uma vez que essas bibliotecas padrão são sempre importadas em qualquer projeto Java.
A linguagem Java utiliza pacotes para fazer a organização das classes e recursos.
Uma forma de se pensar em um pacote é pensar em um diretório no sistema de arquivos do Sistema Operacional (SO). O diretório, de forma geral, serve para agrupar um determinado conjunto de arquivos, assim como um pacote em Java serve para agrupar um determinado conjunto de classes. Dessa maneira, os pacotes são utilizados também para se fazer o encapsulamento de um grupo de classes.
O separador utilizado na organização de um pacote é o ponto (.). Conforme dito acima, no sistema de arquivos do computador, um pacote corresponde a um diretório, ou seja, cada vez que você utiliza um ponto para separar um nome de pacote, um novo diretório é criado no computador. Por exemplo, o pacote code.struct possui duas pastas, uma chamada code e outra struct (que fica dentro da pasta code). Uma dica para entender melhor o que foi explicado é fazer navegação por meio do sistema de arquivos do seu computador pelas pastas (pacotes) e, enquanto isso, criar pacotes no Java (por meio da IDE) para ver como ficaram organizados os diretórios do projeto.
Ainda com relação à Figura 2.7, vamos considerar a classe Data que está dentro do pacote code.struct. Em Java, é necessário informar, no início da declaração da classe, em qual pacote ela se encontra. Para isso, a palavra reservada package é utilizada, seguida pelo nome do pacote. Analise o trecho de código abaixo da classe Data.
package code.struct; // definição da localização do pacote da classe
public class Data { // declaração da classe Data
... // restante do código da classe Data
}
No texto acima, destacamos como é feita a organização de pacotes definidos pelo usuário durante o desenvolvimento do seu projeto. A linguagem Java é extremamente rica em bibliotecas nativas ou, ainda, Application Programming Interfaces (APIs) disponíveis para utilização. Dessa maneira, é importante destacarmos também como estão organizados os pacotes nativos da linguagem Java. O pacote nativo mais importante do Java é o java.lang, que agrupa as classes fundamentais para o projeto da linguagem Java, sendo elas: Object, Class, System, Math, String, Double, Float, Integer, etc. (ORACLE, 2020). Alguns outros pacotes importantes do Java são: java.io, java.net e java.util.
Na Figura 2.8 vemos os principais pacotes nativos da linguagem Java.
Na linguagem Java, toda vez que se for utilizar alguma classe que esteja em outro pacote diferente do pacote atual, sua importação deverá ser feita; para isso, será utilizada a palavra reservada import seguida pelo nome do pacote e, por fim, pelo nome da classe. O comando import do Java possui certa semelhança com o comando include da linguagem C. Considere o Quadro 2.14 a seguir com alguns exemplos de importação.
Exemplo de Código |
Descrição sobre as Importações |
---|---|
|
Importação da classe File que está dentro pacote java.io. |
|
Importação da classe Socket do pacote java.net. |
|
Importação da classe Random do pacote java.util. |
|
Importação da classe System do pacote java.lang. OBS: esta importação é a única opcional, pois é feita de forma automática. |
|
Importação de todas as classes do pacote java.io. |
Conforme ressaltado no Quadro 2.14, a única importação que não é necessária é a importação do pacote java.lang, pois já é feita de forma automática pelo compilador do Java. Nesse quadro, percebemos também que podemos utilizar o caractere curinga asterisco (*) (como em import java.io.*;) na importação de todas as classes de um determinado pacote.
Você pode ficar pensando que programar em Java é difícil, pois é preciso saber fazer a importação de pacotes (e muitos pacotes possuem caminhos muito longos) e a indicação correta do nome do pacote em que uma determinada classe está inserida. No entanto, você pode ficar tranquilo, pois a maioria dos IDEs para Java disponíveis hoje em dia faz a busca e a indicação da importação necessária de forma automática, baseadas apenas no nome da Classe. Sendo assim, não é necessário saber os comandos import e package de memória.
Existe um tipo especial de importação que é a importação estática. Por meio dessa importação, podemos fazer a utilização direta de métodos e atributos estáticos sem utilizarmos o nome da classe.
A título de exemplo, vamos considerar o Código 2.11, que utiliza a importação tradicional e a importação estática.
//pacote em que a classe TesteImportacao está.
package code.unidade2.secao3;
//importação tradicional de classes
import java.util.Random;
//importação estática de classes
import static java.lang.System.out;
import static java.lang.Math.*;
public class TesteImportacao {
public static void main(String[] args) {
out.println("Numero(PI) = " + PI);
out.println("Numero(E) = " + E);
out.println("Raiz(25) = " + sqrt(25));
out.println("Pot(2^10) = " + pow(2, 10));
out.println("Log(100) = " + log10(100));
Random rnd = new Random();
out.println("Rnd = " + rnd.nextDouble());
}
}
Na linha 2 do código acima, temos a especificação do pacote, em que se encontra a presente classe chamada de TesteImportacao; em seguida, na linha 5, temos a importação da classe Random, presente no pacote java.util; na linha 8, temos a importação estática do atributo estático out, que está localizado na classe System do pacote java.lang; na linha 9, temos a importação estática de todos os métodos e atributos estáticos presentes na classe Math do pacote java.lang; nas linhas 13 a 19 temos a utilização dos métodos e atributos estáticos importados — repare que a utilização das constantes PI e E foi feita de forma direta. Repare também que as chamadas aos métodos estáticos sqrt, que calcula a raiz quadrada, pow, que calcula a potência, e log10, que calcula o logaritmo na base 10, foram feitas de forma direta, sem a utilização da classe Math ponto (.) nome do método. Por fim, nas linhas 18 e 19, temos a declaração de um objeto (rnd) da classe Random seguida pela geração de um número aleatório entre 0 e 1, obtido por meio do método nextDouble.
É um bom hábito de programação estudar as bibliotecas nativas já disponíveis para uso, assim como procurar bibliotecas de terceiros que possam resolver o problema no qual estamos lidando. Vale lembrar que alguns desenvolvedores preferem desenvolver a sua própria solução e, assim, evitar a utilização de bibliotecas de terceiros. Essa segunda alternativa não é ruim, mas pode levar um maior tempo no desenvolvimento. Uma dica de onde consultar e baixar bibliotecas de terceiros é o MVN Repository
De forma a entender como pesquisar e incorporar uma biblioteca feita por outra pessoa em seu projeto, acesse o site MVN Repository e, em seguida, busque a seguinte biblioteca: apache commons math. Então, procure a versão mais recente da biblioteca e baixe o arquivo .jar disponível. Após isso, o arquivo deverá se chamar algo como commons-math3-3.6.1.jar, e para que possa incluir/importar esse arquivo .jar, um projeto novo na sua IDE de preferência deverá ser criado (observação: os passos para isso não foram mostrados pois, em cada IDE, os passos são ligeiramente diferentes). Após esses passos, desenvolva um código similar ao Código 2.12 abaixo.
import org.apache.commons.math3.primes.Primes;
public class TesteBiliotecaTerceiros {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
if (Primes.isPrime(i)) {
System.out.println("i = " + i);
}
}
}
}
O código acima utiliza uma biblioteca de matemática desenvolvida pela comunidade para o cálculo de todos os números primos de 0 a 100. Esse foi apenas um exemplo de utilização de biblioteca feita por outras pessoas em que há diversas classes que lidam com problemas relacionados à área de matemática.
Outro assunto muito importante a ser abordado refere-se aos modificadores de acesso. Por meio deles, podemos restringir ou permitir que um atributo, método, construtor e/ou classe seja acessado ou não. Em Java, temos quatro modificadores de acesso, que são: público, privado, protegido e default. O Quadro 2.15 sintetiza esses modificadores.
Modificador |
Palavra-Reservada |
Descrição da Visibilidade |
---|---|---|
Público | public | Aplicável à própria classe e a qualquer outra. |
Protegido | protected | Aplicável à própria classe, outras classes dentro do próprio pacote e dentro de subclasses em outros pacotes. |
Default | - |
Aplicável à própria classe e a outras classes dentro do mesmo pacote. |
Privado | private |
Aplicável à própria classe somente. |
Com base na descrição da visibilidade de cada um dos modificadores, podemos perceber que o modificador privado é o mais restritivo (seguido por default) e protegido; por fim, temos o público, que é o menos restritivo.
Acima, vimos como modificar a visibilidade dos códigos desenvolvidos, a fim de se ocultar/proteger informações que não devem ser acessadas por outras pessoas ou desenvolvedores. Dessa maneira, conseguimos encapsular um código mantendo acessível apenas o que consideramos importante, evitando-se “quebras” ou erros na aplicação. Ao restringir o acesso a algumas informações que visivelmente não são úteis a aplicações externas, simplificamos a utilização das classes desenvolvidas.
A principal ideia do encapsulamento de código é esconder os detalhes de implementação das classes e bibliotecas. De forma geral, assumimos como regra que um objeto nunca deve manipular diretamente os atributos de outro objeto. Em muitos casos, a manipulação direta pode ocasionar erros e gerar muitas inconsistências no programa desenvolvido. Dessa forma, devemos bloquear os atributos utilizando os modificadores de acesso private, default ou, ainda, protected, porém, após o bloqueio dos atributos, como acessá-los e modificá-los? Uma prática comum é a criação de métodos que chamamos de getters e setters ou, ainda, gets e sets. Os métodos gets servem para pegar alguma informação, já os métodos sets servem para definir alguma informação. A Figura 2.9 mostra um esquema didático que representa o encapsulamento de código em nível de classe e em nível de biblioteca/pacote.
A manipulação de uma determinada classe, geralmente, é feita por meio de métodos públicos. Quando necessário, o acesso a constantes pode ser feito de forma direta, ou seja, constantes podem ser declaradas como públicas, principalmente as constantes do tipo static. Acima, foi falado sobre métodos do tipo getters e setters, e para entendermos melhor como declará-los, considere a classe chamada Pessoa, mostrada no Código 2.13.
public class Pessoa {
private int idade;
public int getIdade() {
return idade;
}
public void setIdade(int idade) {
this.idade = idade;
}
}
Em relação ao Código 2.13, queremos destacar que o atributo idade (linha 2) foi declarado como privado, assim, as chamadas para se obter o atributo idade é feita utilizando-se o método público getIdade (definido nas linhas 3 a 5). Caso seja necessário fazer alguma alteração na idade, podemos utilizar o método público setIdade (definido nas linhas 6 a 8). Posteriormente, veremos que podemos/devemos tratar alguns possíveis erros na manipulação de dados. Por exemplo, no método setIdade, poderíamos impedir a atribuição de uma idade negativa, visto que isso não faz sentido na aplicação.
Outro assunto de extrema importância em Java é a herança. A ideia de herança foi brevemente abordada na seção 1 da unidade 2, quando falamos de sobreposição de métodos. A operação de herança envolve duas classes, em que uma das classes é chamada de subclasse e a outra é chamada de superclasse. Nessa operação, a subclasse herda todas as características definidas na superclasse. De forma geral, a subclasse personaliza a superclasse ao adicionar novos recursos e aspectos próprios. A linguagem Java suporta apenas herança simples, em que pode haver apenas uma superclasse. Linguagens como C++ e Python suportam herança múltipla, em que pode haver mais de uma superclasse. A fim de que possa compreender melhor o conceito de herança, vamos considerar o seguinte exemplo mostrado na Figura 2.10.
Na Figura 2.10, as classes Círculo, Triângulo e Retângulo herdam as características da classe Geom2D. Por sua vez, as classes Esfera, Tetraedro e Cubo herdam as características da classe Geom3D. Por fim, as Classes Geom2D e Geom3D herdam, implicitamente (automaticamente), as características da classe Object.
O código 2.14 nos mostra a sintaxe básica usada na implementação de uma subclasse e uma superclasse qualquer.
class SuperClasse {
... //código associado a superclasse
}
class SubClasse extends SuperClasse { //operação de herança
... //código associado a subclasse
}
Em Java, todas as classes são automaticamente descendentes da classe Object. Essa herança é feita de forma implícita e, dessa forma, todos os métodos disponíveis em Object estão disponíveis em todas as outras classes implementadas do Java. Alguns exemplos de métodos disponíveis são: toString, equals, clone e hashCode.
O Código 2.15 mostra a implementação do esquema apresentado na Figura 2.10.
public class Geom2D { //herda implicitamente a classe Object
protected double perimetro;
protected double area;
public double calcPerimetro(){
return 0; //não existe perímetro em objeto abstrato
}
public double calcArea(){
return 0; //não existe área em objeto abstrato
}
}
public class Geom3D { //herda implicitamente a classe Object
protected double area;
protected double volume;
public double calcArea(){
return 0; //não existe área em objeto abstrato
}
public double calcVolume(){
return 0; //não existe volume em objeto abstrato
}
}
public class Retangulo extends Geom2D {
private final double base;
private final double altura;
public Retangulo(double lado) {
this.base = lado;
this.altura = lado;
}
public Retangulo(double base, double altura) {
this.base = base;
this.altura = altura;
}
@Override
public double calcPerimetro() {
super.perimetro = 2 * this.base + 2 * this.altura;
return super.perimetro;
}
@Override
public double calcArea() {
super.area = this.base * this.altura;
return super.area;
}
@Override
public String toString() {
return String.format("Rect:{\n peri: %.2f\n area: %.2f\n}",
this.calcPerimetro(), this.calcArea());
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Retangulo) {
Retangulo r = (Retangulo)obj;
return (base == r.base) && (altura == r.altura);
}else{
return false;
}
}
public static void main(String[] args) {
Retangulo rect1 = new Retangulo(4, 25);
Retangulo rect2 = new Retangulo(4, 26);
System.out.println(rect1); // invoca método toString
System.out.println(rect2); // invoca método toString
if (rect1.equals(rect2)) { // invoca método equals
System.out.println("Figuras Geométricas Iguais");
} else {
System.out.println("Figuras Geométricas Diferentes");
}
}
}
public class Cubo extends Geom3D {
private final double lado;
public Cubo(double lado) {
this.lado = lado;
}
@Override
public double calcArea() {
super.area = 6 * this.lado * this.lado;
return super.area;
}
@Override
public double calcVolume() {
super.volume = this.lado * this.lado * this.lado;
return super.volume;
}
@Override
public String toString() {
return String.format("Cubo:{\n area: %.2f\n vol: %.2f\n}",
this.calcArea(), this.calcVolume());
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Cubo){
Cubo cubo = (Cubo)obj;
return this.lado == cubo.lado;
}else{
return false;
}
}
public static void main(String[] args) {
Cubo cubo1 = new Cubo(1);
Cubo cubo2 = new Cubo(1);
System.out.println(cubo1); // invoca método toString
System.out.println(cubo2); // invoca método toString
if (cubo1.equals(cubo2)) { // invoca método equals
System.out.println("Figuras Geométricas Iguais");
} else {
System.out.println("Figuras Geométricas Diferentes");
}
}
}
Acima, nas linhas 1 a 10, temos a definição da classe Geom2D. Inicialmente, temos, nessa classe, dois atributos principais, que são o perímetro e a área. Como sabemos que toda figura geométrica 2D deve saber o seu perímetro, assim, definimos o método calcPerimetro. Da mesma forma, toda figura 2D deve saber a sua área, assim, definimos o método calcArea. Por enquanto, colocamos esses métodos apenas retornando um valor 0, mas, na próxima seção, mostraremos como alterar essa classe para se tornar uma classe abstrata, e então, teremos apenas a assinatura do método melhorando a legibilidade da implementação.
De forma semelhante à classe Geom2D, temos a classe Geom3D, que está definida nas linhas 12 a 21. Nessa classe, temos os atributos área e volume, que são atributos comuns de figuras geométricas 3D.
Já nas linhas 23 a 68, temos a definição da classe Retangulo. Repare que essa classe herda todas as características de Geom2D por meio da palavra-reservada extends. A classe Retangulo possui dois atributos, que são base e altura. Em seguida, foram definidos dois construtores, e os métodos calcPerimetro e calcArea foram sobrescritos/sobrepostos. A ideia de fazer a sobreposição de métodos vem do fato de que somente a classe Retangulo sabe como calcular o seu perímetro e a sua área. O método toString (lembre-se: esse método pertence à classe Object) foi sobrescrito nas linhas 44 a 48 e auxilia no momento de se fazer a impressão de informações da classe na tela; em seguida, o método equals (lembre-se: esse método também pertence à classe Object) foi sobrescrito nas linhas 49 a 57, a fim de auxiliar na comparação entre dois objetos; de forma geral, desejamos que dois objetos do mesmo tipo e que contêm o mesmo conteúdo sejam considerados iguais. Na linha 51, por sua vez, foi utilizada a palavra reservada instanceof, que podemos ler da seguinte forma: “é uma instância de" ou “é um". O comando instanceof é um operador que compara o tipo de uma variável a uma classe. No código acima, o tipo da variável obj é comparado com a classe Retangulo, então, na linha 52, foi feita uma operação de casting, de forma a converter o tipo Object (tipo menos específico) em tipo Retangulo (tipo mais específico). Para fecharmos o método equals, repare que foi considerado que dois retângulos são iguais quando possuem mesma base e mesma altura. Por fim, nas linhas 58 a 69, foi criado um método main para se testar a classe Retangulo. Aqui, temos dois pontos importantes a serem destacados:
Gostaria de propor a você uma reflexão sobre a importância da sobrescrita dos métodos toString e equals. Talvez, ainda mais importante, seja descobrir, em nossa aplicação, quando devemos sobrescrever esses métodos. Gostaria de destacar que a implementação do método equals pode ser feita de diversas formas e que a escolha de uma forma ou outra é uma questão que deve ser respondida durante o projeto do software. Por exemplo, imagine uma classe que modele a entidade Pessoa: como definiremos que dois objetos do tipo Pessoa são iguais? Um primeiro jeito, não muito bom, pode ser utilizando-se o atributo nome da pessoa, porém todos sabemos que existem pessoas homônimas, dessa maneira, uma outra forma é utilizando-se o atributo CPF, que sabemos ser único.
Ainda com relação ao Código 2.15, nas linhas 71 a 101, temos a definição da classe Cubo, que herda todas as características de Geom3D. A explicação do funcionamento dessa classe é semelhante à classe Retangulo, logo, ela não será explicada. O restante das classes mostradas na Figura 2.10 não será exibido, mas gostaríamos de propor ao aluno que as implemente, pois, assim, terá a oportunidade de fixar o conteúdo.
A operação de herança também pode ser estudada e entendida a partir de algumas classes nativas do Java, como mostrado na Figura 2.11. Algumas classes foram declaradas como final, de forma a impedir que outras classes tentem herdá-las. Por exemplo, pelo fato de a classe String ser declarada como final, não se pode criar uma classe que herde String. Ainda na Figura 2.11, percebemos que a classe Number foi declarada como abstract, isso implica a não construção de um objeto do tipo Number, ou seja, ela é uma classe abstrata. Estudaremos mais sobre classes abstratas na unidade seguinte.
Assim como o encapsulamento e a herança, o polimorfismo é um dos pilares da Orientação a Objetos (OO). No polimorfismo, o tipo de variável de referência pode ser de um tipo diferente do tipo do objeto, mas, para isso, é necessário que a variável de referência seja uma superclasse do tipo do objeto. A ideia do polimorfismo está no fato de que uma referência pode assumir diversas formas diferentes. O polimorfismo caracteriza-se também por fazer com que duas ou mais classes tenham métodos com o mesmo nome, de forma que uma função possa utilizar um objeto de qualquer uma das classes polimórficas. Diante do que foi exposto, as seguintes linhas de código são possíveis e demostram comportamentos polimórficos:
//Variável de Referência → Tipo do Objeto
Geom2D geom1 = new Circulo(1);
Geom2D geom2 = new Triangulo(3, 4, 5);
Geom2D geom3 = new Retangulo(4, 10);
Nos exemplos acima, as variáveis de referência são geom1, geom2 e geom3, que são do tipo Geom2D. Por sua vez, os tipos de objetos são, respectivamente, Circulo, Triangulo e Retangulo. Aqui, é importante reparar que Geom2D é uma superclasse dos tipos de objetos, e de forma a tornar mais clara a ideia do polimorfismo, considere o exemplo de Código 2.17 mostrado a seguir.
public class TestePolimorfismo {
public static void main(String[] args) {
Geom2D objCirculo = new Circulo(2.9);
objCirculo.calcPerimetro();
Geom2D objTriangulo = new Triangulo(6);
objTriangulo.calcPerimetro();
Geom2D maxPeri = maxPerimetro(objCirculo, objTriangulo);
System.out.println("Maior Perimetro: " + maxPeri);
if (maxPeri instanceof Circulo){
Circulo max = (Circulo)maxPeri;
System.out.println("Max Circulo: " + max);
} else if (maxPeri instanceof Triangulo){
Triangulo max = (Triangulo)maxPeri;
System.out.println("Max Triangulo: " + max);
}
}
public static Geom2D maxPerimetro(Geom2D geom1, Geom2D geom2){
if (geom1.getPerimetro() > geom2.getPerimetro()) {
return geom1;
} else {
return geom2;
}
}
}
No Código 2.17, em específico nas linhas 3 e 5, temos a declaração de dois objetos polimórficos. Na linha 7, temos a chamada a um método que calcula e retorna o objeto com maior perímetro entre duas figuras geométricas 2D. Por fim, das linhas 9 a 15, temos um processamento feito baseado no tipo de objeto retornado; aqui, o tipo de objeto mais genérico é convertido para um tipo de objeto mais específico, e essa é uma das vantagens do polimorfismo: a capacidade que um objeto possui de assumir diversas formas diferentes.
Caro estudante, nesta seção, você estudou os conteúdos relacionados à organização de pacotes, à importação de pacotes, a modificadores de acesso, a encapsulamento, à herança e a polimorfismo, bem como foi mostrado diversos exemplos sobre a implementação desses conceitos em Java. Na seção seguinte, estudaremos o tratamento de exceção e o uso de classes abstratas, o que nos possibilitará avançar ainda mais no entendimento da linguagem Java.
ARANTES, J. da S. Github. 2020. Disponível em: https://bit.ly/3eiUMcF. Acesso em: 20 mai. 2020.
ASLAM, A. Access Modifiers in Java. SlideShare, publicado em 28 fev. 2017. Disponível em: https://bit.ly/2QpeVV3. Acesso em: 22 jul. 2020.
CAELUM. Java e orientação a objetos: curso FJ-11. [S.l.]: Caleum, [s.d.]. Disponível em: https://bit.ly/3iJX34d. Acesso em: 17 mai. 2020.
CURSO de Java #01 - História do Java - Gustavo Guanabara. [S.l.: s.n.], 2015. 1 vídeo (36 min). Publicado pelo canal Curso em Vídeo. Disponível em: https://bit.ly/2YvIJDZ. Acesso em: 22 jul. 2020.
CURSO POO Teoria #12a - Conceito Polimorfismo (Parte 1). [S.l.: s.n.]. 1 vídeo (28:42 min). Publicado pelo canal Curso em Vídeo. Disponível em: https://bit.ly/2YBc0wW. Acesso em: 21 jul. 2020.
CURSO POO Teoria #13a - Conceito Polimorfismo (Parte 2). [S.l.: s.n.]. 1 vídeo (20:14 min). Publicado pelo canal Curso em Vídeo. Disponível em: https://bit.ly/3b0hF4H. Acesso em: 21 jul. 2020.
DEITEL, P. J.; DEITEL, H. M. Java: como programar. 10. ed. São Paulo: Pearson Education, 2016.
GITAHEAD. GitAhead. 2019. Disponível em: https://bit.ly/3aVZihd. Acesso em: 21 jul. 2020.
GITHUB. GitHub. 2020. Disponível em: https://bit.ly/2ZSr1ud. Acesso em: 21 jul. 2020.
GITKRAKEN. GitKraken Git GUI. 2020. Disponível em: https://bit.ly/3huTDRI. Acesso em: 21 jul. 2020.
MVN REPOSITORY. What’s new in maven. 2020. Disponível em: https://bit.ly/34zGEdT. Acesso em: 21 jul. 2020.
ORACLE. Package java.lang. 2018. Disponível em: https://bit.ly/31uQPi3. Acesso em: 21 jul. 2020.
SOURCETREE. Sourcetree. 2020. Disponível em: https://bit.ly/3jlhELx. Acesso em: 21 jul. 2020.
TORTOISEGIT. Tortoise Git. 2020. Windows Shell Interface to Git. Disponível em: https://bit.ly/2YCpvMT. Acesso em: 21 jul. 2020.