A palavra polimorfismo refere-se à qualidade ou estado de ser capaz de assumir diferentes formas.
Em orientação a objetos, o polimorfismo significa possuir diferentes comportamentos.
O polimorfismo permite “programar no geral” em vez de “programar no específico”.
O polimorfismo permite escrever programas que processam objetos que compartilham a mesma superclasse, direta ou indiretamente, como se todos fossem objetos da superclasse.
Ou seja, o polimorfismo é na verdade um conceito consequência da utilização de relações entre tipos, seja via Herança ou via utilização de Interfaces.
Não faria sentido utilizar Herança para criar novas abstrações, se o objeto não pudesse ser visto como o tipo dessas abstrações. É através do polimorfismo que um objeto pode ser visto como qualquer uma de suas abstrações.
Como visto anteriormente, a classe EstudanteGraduacao é subclasse direta de Estudante e subclasse indireta de Usuario em uma hierarquia de heranças.
O conceito de polimorfismo permite que EstudanteGraduacao possa assumir no código a forma de Estudante ou de Usuario de acordo com a necessidade, permitindo “programar no geral” . O mesmo é válido para as subclasses que fazem parte de uma relação de herança e para classes que implmentam interfaces.
O polimorfismo permite-lhe tratar as generalidades e deixar que o ambiente de tempo de execução trate as especificidades.
Você pode instruir objetos a se comportarem de maneiras apropriadas para esses objetos, sem nem mesmo conhecer seus tipos específicos, contanto que os objetos pertençam à mesma hierarquia de herança.
Com o polimorfismo, podemos projetar e implementar sistemas que são facilmente extensíveis:
Novas classes podem ser adicionadas com pouca ou nenhuma modificação a partes gerais do programa, contanto que as novas classes façam parte da hierarquia de herança que o programa processa genericamente.
Novas classes podem implementar interfaces cumprindo seus contratos.
As novas classes simplesmente se “encaixam”.
As únicas partes de um programa que devem ser alteradas são aquelas que exigem conhecimento direto das novas classes que adicionamos à hierarquia.
Em outras palavras, o polimorfismo promove a reutilização e desacoplamento das classes.
O polimorfismo permite escrever programas que processam objetos de subclasses que compartilham a mesma superclasse, direta ou indiretamente, como se todos fossem objetos da superclasse, ou seja, como se todos fossem objetos do mesmo TIPO.
É através do polimorfismo que um objeto pode ser visto como qualquer uma de suas abstrações.
Uma consequência de tipos polimórficos é a possibilidade de type casting (conversão) implícito do subtipo para seus supertipos.
Usuario eg01 = new EstudanteGraduacao();
Estudante eg02 = new EstudanteGraduacao();
EstudanteGraduacao eg03 = new EstudanteGraduacao();
EstudanteGraduacao pode assumir o tipo de EstudanteGraduacao , Estudante e Usuario, sendo considerado um tipo polimórfico.
Assim, todas as formas de declaração de EstudanteGraduacao do exemplo são possíveis. E a conversão de tipos de eg01 e eg02 será feita em tempo de execução implicitamente.
Typecasting, ou só Casting, é um dos conceitos mais importantes na programação, que basicamente significa converter um tipo de dado em outro tipo de dado, seja de forma implícita ou explicita.
Em orientação a objetos, tratamos o typecasting de objetos, que pode ser de dois tipos: upcasting ou downcasting.
Upcasting é fazer um objeto se passar por um objeto que seja um supertipo direto ou indireto dele.
A conversão de tipos de um upcasting pode ser chamada também de promoção de tipo e casting implícito.
O upcasting sempre funcionará já que todo objeto é completamente compatível com o tipo do qual ele foi derivado.
Por isso, o upcasting é feito de forma implícita no código, ou seja, o compilador faz quando for necessário. Como nos exemplos prévios dos objetos eg01 e eg02 .
Object obj = new String("abc");
A conversão do tipo String para o super tipo Object foi feito implicitamente, sem a necessidade de nenhum código adicional, pois o compilador sabe o que fazer.
Usuario eg01;
EstudanteGraduacao eg03 = new EstudanteGraduacao("Maria");
eg01 = eg03;
A conversão pode ser feita no código em qualquer momento, não precisa acontecer no momento da declaração. Como mostra o exemplo de atribuição do objeto eg03 no objeto eg01.
Downcasting é quando o objeto se passa por um subtipo dele.
Também pode ser chamado de demoção de tipo, rebaixamento de tipo ou casting explícito.
Não há garantias que um downcasting possa funcionar, por isso o compilador só irá aceitar se ele puder provar que o objeto se encaixará perfeitamente no subtipo.
Por isso, o downcasting deve ser feito de forma explícita no código, ainda que a conversão só aconteça em tempo de execução.
A sintaxe para realização do downcasting é informar entre parênteses à frente do nome do objeto o nome do supertipo desejado para conversão: (NomeSubtipo)NomeObjetoSupertipo
Object obj = new String("abc");
String str = (String)obj;
System.out.println(str);
A conversão do tipo Object para o subtipo String foi feita explicitamente no código para informar ao compilador qual conversão deve tentar fazer.
Estudante eg02 = new EstudanteGraduacao("Pedro");
EstudanteGraduacao eg03 = new EstudanteGraduacao("Maria");
eg03 = (EstudanteGraduacao)eg02;
System.out.println(eg03.getClass().getName());
A conversão do supertipo Estudante para o subtipo EstudanteGraducao foi realizada para a atribuição de eg02 em eg03.
Um método polimórfico é um método que resulta em diferentes comportamentos dependendo do objeto que está sendo referenciado quando o método é chamado.
Isso acontece quando há a definição de um corpo para um método abstrato ou quando há a sobrescrita de um método de uma superclasse por uma subclasse dentro de uma relação de Herança.
Como um método polimórfico possui diferentes comportamentos implementados, a decisão do comportamento que será executado quando o método polimórfico for chamado será feita em tempo de execução.
Assim, quem chama o método polimórfico (classe cliente) para um determinado objeto não precisa conhecer o subtipo do objeto.
Além disso, a classe cliente também não precisa conhecer o comportamento do método, ou seja, como este método foi implementado.
Uma consequência é que uma coleção de objetos pode responder de maneiras diferentes à mesma mensagem(método) dependendo dos subtipos dos objetos da coleção, permitindo "programar no geral". A decisão sobre qual versão do método deve ser executado é tomada em tempo de execução, considerando o tipo do objeto(instância) que o está chamando.
A JVM sempre vai procurar um método chamado primeiro no objeto que foi instanciado, e apenas quando este método não for encontrado, irá procurar em sua superclasse, conforme o exemplo ilustrado a seguir.
Considere verHistorico() um método polimórfico com comportamentos diferentes para Estudante e EstudantePosGraduacao.
Arquivo Estudante.java
public abstract class Estudante extends Usuario {
protected String RGA;
Estudante(String nome) {
super("Estudante " + nome);
}
public String getRGA() {
return RGA;
}
public void setRGA(String RGA) {
this.RGA = RGA;
}
public void verHistorico() {
System.out.println("Exibindo histórico por semestre...");
}
}
Arquivo EstudanteGraduacao.java
public class EstudanteGraduacao extends Estudante {
EstudanteGraduacao(String nome) {
super("de Graduação "+nome);
}
//essa subclasse não sobrescreve o método verHistorico()
}
Arquivo EstudantePosGraduacao.java
public class EstudantePosGraduacao extends Estudante {
EstudantePosGraduacao(String nome) {
super("de Pós Graduação "+nome);
}
@Override sobrescreve o método verHistorico()
public void verHistorico(){
System.out.println("Exibindo histórico por ano para estudante de pós-graduação...");
}
}
Arquivo Main.java
class Main {
public static void main(String[] args) {
Estudante eg02 = new EstudanteGraduacao("Pedro");
EstudanteGraduacao eg03 = new EstudanteGraduacao("Maria");
EstudantePosGraduacao epg04 = new EstudantePosGraduacao("Carla");
Estudante estudantes[] = {eg02, eg03, epg04};
for (int i = 0; i < estudantes.length; i++) {
estudantes[i].verHistorico();
}
}
}
O polimorfismo permite que um vetor de estudantes (ou qualquer outra coleção de objetos) seja criado, utilizando o supertipo polimórfico Estudante.
O vetor poderá receber quaisquer tipo de estudantes, seja EstudanteGraduacao, EstudantePosGraduacao, ou novos tipos que ainda podem ser criados ao estender Estudante.
Ao chamar o método polimórfico verHistorico() a partir do vetor de estudantes, a JVM irá definir em tempo de execução qual versão do método será executada, iniciando as buscas a partir das implementações dos subtipos, depois do supertipo direto e, assim, sucessivamente até o último supertipo indireto.
No exemplo, eg02 e eg03 irão executar a versão de verHistorico()da classe Estudante. Já o objeto epg04 irá executar a versão de verHistorico() da classe EstudantePosGraduacao.
Não é possível tratar um objeto de superclasse como um objeto de subclasse, porque um objeto de superclasse não é um objeto de quaisquer das suas subclasses. O relacionamento é um é aplicado somente a partir da parte superior da hierarquia de uma subclasse às suas superclasses diretas (e indiretas), e não vice-versa (isto é, não da parte inferior da hierarquia de uma superclasse às suas subclasses ou subclasses indiretas).
O compilador Java não permite a atribuição de uma referência de superclasse a uma variável de subclasse se a referência da superclasse for convertida explicitamente para o tipo da subclasse. Por que iríamos querer realizar essa atribuição? Uma referência de superclasse somente pode ser utilizada para invocar os métodos declarados na superclasse — tentativas de invocar métodos somente de subclasse por meio de uma referência de superclasse resultam em erros de compilação.
Se um programa precisar realizar uma operação específica na subclasse em um objeto de subclasse referenciado por uma variável de superclasse, o programa deverá primeiro fazer um downcasting. Isso permite ao programa invocar métodos de subclasse que não estão na superclasse.
Exemplo de método polimórfico respirar envolvendo herança e interfaces. Fonte: Autoral, feito com Canva para Educação.
Arquivo Main.java
import java.util.ArrayList;
class Main {
public static void main(String[] args) {
Peixe p1 = new PeixePalhaco(); // peixe branquial
Peixe p2 = new Piramboia(); // peixe complementar
Cachorro c1 = new Cachorro(); // mamífero pulmonar
/*
* executando série de respiração coletivamente via método polimórfico
* respirar()
*/
ArrayList<Animal> listaAnimais = new ArrayList<Animal>();
listaAnimais.add(p1);
listaAnimais.add(p2);
listaAnimais.add(c1);
for (int i = 0; i < listaAnimais.size(); i++) {
Animal animal = listaAnimais.get(i);
System.out.println(animal.getClass().getName());
animal.respirar();
}
}
}
Antes de criar uma Herança, verifique se o conceito de polimorfismo faz sentido para as classes da hierarquia, ou seja, se qualquer subclasse pode ser utilizada no lugar da superclasse. Em caso negativo, isso é um indício de que a herança está sendo utilizada de forma inadequada. Essa definição é conhecida como o Princípio de Substituição de Liskov.