Polimorfismo—ou a capacidade de um objeto executar ações especializadas com base em seu tipo — é o que torna o código Java flexível. Muitos padrões de design criados pelo Gang Of Four dependem de alguma forma de polimorfismo, incluindo o padrão Command. Neste artigo, você aprenderá os fundamentos do polimorfismo Java e como usá-lo em seus programas.
Coisas que você deve saber sobre o polimorfismo Java
- Polimorfismo e herança Java
- Por que o polimorfismo é importante
- Polimorfismo na substituição de método
- Polimorfismo com as principais classes Java
- Chamadas e conversão de métodos polimórficos
- Palavras-chave reservadas e polimorfismo
- Erros comuns com polimorfismo
- O que lembrar sobre polimorfismo
Polimorfismo e herança Java
Vamos nos concentrar na relação entre polimorfismo e herança Java. A principal coisa a ter em mente é que o polimorfismo requer uma herança ou implementação de interface. Você pode ver isso no exemplo abaixo, apresentando Duke e Juggy:
public abstract class JavaMascot {
public abstract void executeAction();
}
public class Duke extends JavaMascot {
@Override
public void executeAction() {
System.out.println("Punch!");
}
}
public class Juggy extends JavaMascot {
@Override
public void executeAction() {
System.out.println("Fly!");
}
}
public class JavaMascotTest {
public static void main(String... args) {
JavaMascot dukeMascot = new Duke();
JavaMascot juggyMascot = new Juggy();
dukeMascot.executeAction();
juggyMascot.executeAction();
}
}
A saída deste código será:
Punch!
Fly!
Devido às suas implementações específicas, ambos Duke
'areia Juggy
as ações serão executadas.
Por que o polimorfismo é importante
O objetivo do uso do polimorfismo é dissociar a classe cliente do código de implementação. Em vez de ser codificada, a classe cliente recebe a implementação para executar a ação necessária. Dessa forma, a classe cliente sabe apenas o suficiente para executar suas ações, o que é um exemplo de acoplamento fraco.
Para entender melhor as vantagens do polimorfismo, dê uma olhada no SweetCreator
:
public abstract class SweetProducer {
public abstract void produceSweet();
}
public class CakeProducer extends SweetProducer {
@Override
public void produceSweet() {
System.out.println("Cake produced");
}
}
public class ChocolateProducer extends SweetProducer {
@Override
public void produceSweet() {
System.out.println("Chocolate produced");
}
}
public class CookieProducer extends SweetProducer {
@Override
public void produceSweet() {
System.out.println("Cookie produced");
}
}
public class SweetCreator {
private List sweetProducer;
public SweetCreator(List sweetProducer) {
this.sweetProducer = sweetProducer;
}
public void createSweets() {
sweetProducer.forEach(sweet -> sweet.produceSweet());
}
}
public class SweetCreatorTest {
public static void main(String... args) {
SweetCreator sweetCreator = new SweetCreator(Arrays.asList(new CakeProducer(),
new ChocolateProducer(), new CookieProducer()));
sweetCreator.createSweets();
}
}
Neste exemplo, você pode ver que o SweetCreator
classe só conhece o SweetProducer
aula. Não conhece a implementação de cada Sweet
. Essa separação nos dá flexibilidade para atualizar e reutilizar nossas classes e torna o código muito mais fácil de manter. Ao projetar seu código, sempre procure maneiras de torná-lo o mais flexível e fácil de manter possível. O polimorfismo é uma técnica muito poderosa para escrever código Java reutilizável.
Dica: O @Override
a anotação obriga o programador a usar a mesma assinatura de método que deve ser substituída. Se o método não for substituído, ocorrerá um erro de compilação.
O método está sobrecarregando o polimorfismo?
Muitos programadores ficam confusos sobre a relação do polimorfismo com a substituição e sobrecarga de métodos. Entretanto, apenas a substituição de método é o verdadeiro polimorfismo. A sobrecarga compartilha o mesmo nome do método, mas os parâmetros são diferentes. Polimorfismo é um termo amplo, então sempre haverá discussões sobre esse tema,
Polimorfismo na substituição de método
É possível alterar o tipo de retorno de um método substituído se for um tipo covariante. A tipo covariante é essencialmente uma subclasse do tipo de retorno. Aqui está um exemplo:
public abstract class JavaMascot {
abstract JavaMascot getMascot();
}
public class Duke extends JavaMascot {
@Override
Duke getMascot() {
return new Duke();
}
}
Porque Duke
é um JavaMascot
podemos alterar o tipo de retorno ao substituir.
Polimorfismo com as principais classes Java
Usamos polimorfismo o tempo todo nas principais classes Java. Um exemplo muito simples é quando instanciamos o ArrayList
classe declarando o List
interface como um tipo:
List list = new ArrayList<>();
Para ir mais longe, considere este exemplo de código usando a API Java Collections sem polimorfismo:
public class ListActionWithoutPolymorphism {
// Example without polymorphism
void executeVectorActions(Vector
Esse é um código feio, não é? Imagine tentar mantê-lo! Agora veja o mesmo exemplo com polimorfismo:
public static void main(String … polymorphism) {
ListAction listAction = new ListAction();
listAction.executeListActions();
}
public class ListAction {
void executeListActions(List
Os benefícios do polimorfismo são flexibilidade e extensibilidade. Em vez de criar vários métodos diferentes, podemos declarar apenas um método que recebe o genérico List
tipo.
Chamadas e conversão de métodos polimórficos
É possível invocar métodos específicos em uma chamada polimórfica, mas isso acarreta um custo de flexibilidade. Aqui está um exemplo:
public abstract class MetalGearCharacter {
abstract void useWeapon(String weapon);
}
public class BigBoss extends MetalGearCharacter {
@Override
void useWeapon(String weapon) {
System.out.println("Big Boss is using a " + weapon);
}
void giveOrderToTheArmy(String orderMessage) {
System.out.println(orderMessage);
}
}
public class SolidSnake extends MetalGearCharacter {
void useWeapon(String weapon) {
System.out.println("Solid Snake is using a " + weapon);
}
}
public class UseSpecificMethod {
public static void executeActionWith(MetalGearCharacter metalGearCharacter) {
metalGearCharacter.useWeapon("SOCOM");
// The below line wouldn't work
// metalGearCharacter.giveOrderToTheArmy("Attack!");
if (metalGearCharacter instanceof BigBoss) {
((BigBoss) metalGearCharacter).giveOrderToTheArmy("Attack!");
}
}
public static void main(String... specificPolymorphismInvocation) {
executeActionWith(new SolidSnake());
executeActionWith(new BigBoss());
}
}
A técnica que estamos usando aqui é fundiçãoou alterar deliberadamente o tipo de objeto em tempo de execução.
Observe que é possível invocar um método específico apenas ao converter o tipo genérico para o tipo específico. Uma boa analogia seria dizer explicitamente ao compilador: “Ei, eu sei o que estou fazendo aqui, então vou converter o objeto para um tipo específico e usar um método específico”.
Referindo-se ao exemplo acima, há uma razão importante para o compilador se recusar a aceitar a invocação de um método específico: a classe que está sendo passada pode ser SolidSnake
. Neste caso, não há como o compilador garantir que cada subclasse de MetalGearCharacter
tem o giveOrderToTheArmy
método declarado.
Obtenha o código: Obtenha o código-fonte deste desafio e execute seus próprios testes.
Palavras-chave reservadas
Preste atenção à palavra reservada instanceof
. Antes de invocar o método específico, perguntamos se MetalGearCharacter
é “instanceof
” BigBoss
. Se isso não foi a BigBoss
Por exemplo, receberíamos a seguinte mensagem de exceção:
Exception in thread "main" java.lang.ClassCastException: com.javaworld.javachallengers.polymorphism.specificinvocation.SolidSnake cannot be cast to com.javaworld.javachallengers.polymorphism.specificinvocation.BigBoss
E se quiséssemos fazer referência a um atributo ou método de uma superclasse Java? Neste caso, poderíamos usar o super
palavra reservada. Por exemplo:
public class JavaMascot {
void executeAction() {
System.out.println("The Java Mascot is about to execute an action!");
}
}
public class Duke extends JavaMascot {
@Override
void executeAction() {
super.executeAction();
System.out.println("Duke is going to punch!");
}
public static void main(String... superReservedWord) {
new Duke().executeAction();
}
}
Usando a palavra reservada super
em Duke
de executeAction
método invoca o método da superclasse. Em seguida, executamos a ação específica de Duke
. É por isso que podemos ver ambas as mensagens na saída abaixo:
The Java Mascot is about to execute an action!
Duke is going to punch!
Erros comuns com polimorfismo
É um erro comum pensar que é possível invocar um método específico sem usar conversão.
Outro erro é não ter certeza de qual método será invocado ao instanciar uma classe polimorficamente. Lembre-se que o método a ser invocado é o método da instância criada.
Lembre-se também de que a substituição de método não é uma sobrecarga de método.
É impossível substituir um método se os parâmetros forem diferentes. Isto é possível para alterar o tipo de retorno do método substituído se o tipo de retorno for uma subclasse do método da superclasse.
O que lembrar sobre polimorfismo
- A instância criada determinará qual método será invocado ao usar o polimorfismo.
- O
@Override
a anotação obriga o programador a usar um método substituído; caso contrário, haverá um erro do compilador. - O polimorfismo pode ser usado com classes normais, classes abstratas e interfaces.
- A maioria dos padrões de projeto depende de alguma forma de polimorfismo.
- A única maneira de usar um método específico em sua subclasse polimórfica é usando conversão.
- É possível projetar uma estrutura poderosa em seu código usando polimorfismo.
Aceite o desafio do polimorfismo!
Vamos testar o que você aprendeu sobre polimorfismo e herança. Neste desafio, você recebe alguns métodos do livro de Matt Groening Os Simpsons, e seu desafio é deduzir qual será o resultado de cada classe. Para começar, analise cuidadosamente o seguinte código:
public class PolymorphismChallenge {
static abstract class Simpson {
void talk() {
System.out.println("Simpson!");
}
protected void prank(String prank) {
System.out.println(prank);
}
}
static class Bart extends Simpson {
String prank;
Bart(String prank) { this.prank = prank; }
protected void talk() {
System.out.println("Eat my shorts!");
}
protected void prank() {
super.prank(prank);
System.out.println("Knock Homer down");
}
}
static class Lisa extends Simpson {
void talk(String toMe) {
System.out.println("I love Sax!");
}
}
public static void main(String... doYourBest) {
new Lisa().talk("Sax :)");
Simpson simpson = new Bart("D'oh");
simpson.talk();
Lisa lisa = new Lisa();
lisa.talk();
((Bart) simpson).prank();
}
}
O que você acha? Qual será o resultado final? Não use um IDE para descobrir isso! O objetivo é melhorar suas habilidades de análise de código, então tente determinar a saída por si mesmo.
Escolha sua resposta e você encontrará a resposta correta abaixo.
A)
I love Sax!
D'oh
Simpson!
D'oh
B)
Sax :)
Eat my shorts!
I love Sax!
D'oh
Knock Homer down
C)
Sax :)
D'oh
Simpson!
Knock Homer down
D)
I love Sax!
Eat my shorts!
Simpson!
D'oh
Knock Homer down
Resolvendo o desafio
Para a seguinte invocação de método:
new Lisa().talk("Sax :)");
a saída será “I love Sax!
“Isso ocorre porque estamos passando por um String
para o método e Lisa
tem o método.
Para a próxima invocação:
Simpson simpson = new Bart("D'oh");
simpson.talk();
A saída será “Eat my shorts!
“Isso ocorre porque estamos instanciando o Simpson
digite com Bart
.
Agora verifique este, que é um pouco mais complicado: