Desafiadores de Java # 3: Polimorfismo e herança

Desafiadores de Java # 3: Polimorfismo e herança


Continuamos a traduzir uma série de artigos com problemas de Java. O último post sobre as linhas causou uma discussão surpreendentemente acalorada . Esperamos que você também não passe por este artigo. E sim - agora estamos convidando você para o décimo aniversário do nosso curso Java Developer .


De acordo com o lendário Venkat Subramaniam, o polimorfismo é o conceito mais importante na programação orientada a objetos. Polimorfismo - ou a capacidade de um objeto de executar ações especializadas com base em seu tipo - é o que torna o código Java flexível. Padrões de design, como o Comando, o Observador, o Decorador, a Estratégia e muitos outros criados pelo Gangue dos Quatro, todos usam alguma forma de polimorfismo. Dominar esse conceito melhorará muito sua capacidade de pensar em soluções de software.



Você pode pegar o código fonte deste artigo e experimentar aqui: https://github.com/rafadelnero/javaworld-challengers


Interfaces e herança no polimorfismo


Neste artigo, focaremos na relação entre polimorfismo e herança. O principal a ter em mente é que o polimorfismo requer herança ou implementação de uma interface . Você pode ver isso no exemplo abaixo com 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á assim:


 Punch! Fly! 

Como implementações específicas são definidas, os métodos Duke e Juggy serão chamados.


O método sobrecarrega um polimorfismo? Muitos programadores confundem a relação do polimorfismo com a substituição e a sobrecarga . De fato, apenas redefinir o método é polimorfismo verdadeiro. A sobrecarga usa o mesmo nome do método, mas parâmetros diferentes. Polimorfismo é um termo amplo, portanto sempre haverá discussões sobre esse tópico.


Qual é o objetivo do polimorfismo


Uma grande vantagem e objetivo de usar o polimorfismo é reduzir a conexão da classe cliente com a implementação. Em vez de código fixo, a classe cliente obtém uma implementação da dependência para executar a ação necessária. Portanto, a classe cliente sabe o mínimo para concluir suas ações, que é um exemplo de ligação fraca.


Para entender melhor o objetivo 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> sweetProducer; public SweetCreator(List<SweetProducer> 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 a classe SweetCreator conhece SweetCreator a classe SweetProducer . Ele não conhece a implementação de todo Sweet . Essa separação nos dá a flexibilidade de atualizar e reutilizar nossas classes, e isso facilita muito a manutenção do código. Ao projetar o código, sempre procure maneiras de torná-lo o mais flexível e conveniente possível. O polimorfismo é uma maneira muito poderosa de ser usada para esse fim.


A @Override requer que o programador use a mesma assinatura de método, que deve ser redefinida. Se o método não for substituído, haverá um erro de compilação.

Tipos de retorno covariantes ao substituir um método


Você pode alterar o tipo de retorno de um método substituído, se for do tipo covariante . O tipo covariante é basicamente uma subclasse do valor de retorno.


Considere um exemplo:


 public abstract class JavaMascot { abstract JavaMascot getMascot(); } public class Duke extends JavaMascot { @Override Duke getMascot() { return new Duke(); } } 

Como Duke é JavaMascot , podemos alterar o tipo do valor de retorno ao substituir.


Polimorfismo nas classes base Java


Usamos constantemente o polimorfismo nas classes Java básicas. Um exemplo muito simples é instanciar uma classe ArrayList com uma declaração de tipo como uma interface de List .


 List<String> list = new ArrayList<>(); 

Considere um código de amostra que usa a API Java Collections sem polimorfismo:


 public class ListActionWithoutPolymorphism { //    void executeVectorActions(Vector<Object> vector) {/*    */} void executeArrayListActions(ArrayList<Object> arrayList) {/*    */} void executeLinkedListActions(LinkedList<Object> linkedList) {/*    */} void executeCopyOnWriteArrayListActions(CopyOnWriteArrayList<Object> copyOnWriteArrayList) { /*    */} } public class ListActionInvokerWithoutPolymorphism { listAction.executeVectorActions(new Vector<>()); listAction.executeArrayListActions(new ArrayList<>()); listAction.executeLinkedListActions(new LinkedList<>()); listAction.executeCopyOnWriteArrayListActions(new CopyOnWriteArrayList<>()); } 

Código nojento, certo? Imagine que você precisa acompanhá-lo! Agora considere o mesmo exemplo com o polimorfismo:


 public static void main(String... polymorphism) { ListAction listAction = new ListAction(); listAction.executeListActions(); } public class ListAction { void executeListActions(List<Object> list) { //      } } public class ListActionInvoker { public static void main(String... masterPolymorphism) { ListAction listAction = new ListAction(); listAction.executeListActions(new Vector<>()); listAction.executeListActions(new ArrayList<>()); listAction.executeListActions(new LinkedList<>()); listAction.executeListActions(new CopyOnWriteArrayList<>()); } } 

A vantagem do polimorfismo é flexibilidade e extensibilidade. Em vez de criar vários métodos diferentes, podemos declarar um método que obtém um tipo de List .


Chamando métodos específicos para um método polimórfico


Você pode chamar métodos específicos com uma chamada de método polimórfica, isso é devido à 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"); //      // 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 usamos aqui é converter ou alterar conscientemente o tipo de um objeto em tempo de execução.


Observe que uma chamada para um método específico é possível apenas ao converter um tipo mais geral para um tipo mais 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 usarei esse método".


Referindo-se ao exemplo acima, o compilador tem um bom motivo para não aceitar a chamada de certos métodos: a classe que é passada deve ser SolidSnake . Nesse caso, o compilador não tem como garantir que todas as subclasses de MetalGearCharacter tenham um método giveOrderToTheArmy .


Instância da palavra-chave


Observe a palavra reservada instanceof . Antes de chamar um método específico, perguntamos se MetalGearCharacter instância do BigBoss . Se essa não é uma instância do BigBoss , obteremos a seguinte 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 

Palavra super chave super


E se quisermos nos referir a um atributo ou método da classe pai? Nesse caso, podemos usar a super palavra super chave.
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(); } } 

O uso da palavra reservada super no método executeAction da classe Duke chama o método da classe pai. Em seguida, executamos uma ação específica da classe Duke . É por isso que podemos ver as duas mensagens na saída:


 The Java Mascot is about to execute an action! Duke is going to punch! 

Resolver o problema do polimorfismo


Vamos verificar o que você aprendeu sobre polimorfismo e herança.


Nesta tarefa, você recebe vários métodos de Os Simpsons, de Matt Groening, dos Wavs necessários para desvendar qual será o resultado de cada classe. Primeiro, 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? Não use o IDE para descobrir! O objetivo é melhorar suas habilidades de análise de código, então tente decidir por si mesmo.


Escolha sua resposta (você pode encontrar a resposta correta no final do artigo).


A)
Eu amo sax!
D'oh
Simpson!
D'oh


B)
Sax :)
Coma minha bermuda!
Eu amo sax!
D'oh
Bata no local


C)
Sax :)
D'oh
Simpson!
Bata no local


D)
Eu amo sax!
Coma minha bermuda!
Simpson!
D'oh
Bata no local


O que aconteceu Entendendo o polimorfismo


Para a seguinte chamada de método:


 new Lisa().talk("Sax :)"); 

a saída será "Eu amo Sax!". Isso ocorre porque passamos a string para o método e a classe Lisa possui esse método.


Para a próxima chamada:


 Simpson simpson = new Bart("D'oh"); simpson.talk(); 

A saída será "Eat my shorts!". Isso ocorre porque inicializamos o tipo Simpson com Bart .


Agora veja, isso é um pouco mais complicado:


 Lisa lisa = new Lisa(); lisa.talk(); 

Aqui usamos método sobrecarregando com herança. Como não passamos nada para o método de talk , o método de talk de Simpson chamado.


Nesse caso, a saída será "Simpson!".


Aqui está outro:


 ((Bart) simpson).prank(); 

Nesse caso, a sequência de prank foi passada ao instanciar a classe Bart por meio do new Bart("D'oh"); . Nesse caso, o método super.prank() é chamado primeiro e, em seguida, o método prank() da classe Bart . A conclusão será:


 "D'oh" "Knock Homer down" 

Erros comuns de polimorfismo


Um erro comum é pensar que você pode chamar um método específico sem usar conversão de tipo.


Outro erro é a incerteza sobre qual método será chamado ao instanciar polimorficamente a classe. Lembre-se de que o método que está sendo chamado é o método da instância instanciada.


Lembre-se também de que substituir um método não é uma sobrecarga de um método.


Não é possível substituir o método se os parâmetros forem diferentes. Você pode alterar o tipo de retorno de um método substituído se o tipo de retorno for uma subclasse.


O que você precisa se lembrar sobre o polimorfismo


  • A instância criada determina qual método será chamado ao usar o polimorfismo.


  • A @Override requer que o programador use um método substituído; caso contrário, ocorrerá um erro do compilador.


  • O polimorfismo pode ser usado com classes regulares, classes abstratas e interfaces.


  • A maioria dos padrões de design depende de alguma forma de polimorfismo.


  • A única maneira de chamar seu método em uma subclasse polimórfica é usar conversão de tipo.


  • Você pode criar uma estrutura de código poderosa usando polimorfismo.


  • Experiência. Com isso, você será capaz de dominar esse poderoso conceito!



A resposta


A resposta é D.


A conclusão será:


 I love Sax! Eat my shorts! Simpson! D'oh Knock Homer down 

Como sempre, congratulo-me com seus comentários e perguntas. E estamos esperando Vitaly em uma lição aberta .

Source: https://habr.com/ru/post/pt429120/


All Articles