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 Juggyas 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 JavaMascotpodemos 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 é “instanceofBigBoss. 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 Dukede 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: