Polimorfismo ed ereditarietà in Java

Secondo la leggenda Venkat Subramaniam, il polimorfismo è il concetto più importante nella programmazione orientata agli oggetti. Il polimorfismo, ovvero la capacità di un oggetto di eseguire azioni specializzate in base al suo tipo, è ciò che rende flessibile il codice Java. Modelli di design come Command, Observer, Decorator, Strategy e molti altri creati dalla Gang Of Four, utilizzano tutti una qualche forma di polimorfismo. La padronanza di questo concetto migliora notevolmente la tua capacità di pensare attraverso soluzioni alle sfide di programmazione.

Ottieni il codice

Puoi ottenere il codice sorgente per questa sfida ed eseguire i tuoi test qui: //github.com/rafadelnero/javaworld-challengers

Interfacce ed eredità nel polimorfismo

Con questo Java Challenger, ci stiamo concentrando sulla relazione tra polimorfismo ed ereditarietà. La cosa principale da tenere a mente è che il polimorfismo richiede l' ereditarietà o l'implementazione dell'interfaccia . Puoi vederlo nell'esempio qui sotto, con 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(); } } 

L'output di questo codice sarà:

 Punch! Fly! 

A causa delle loro implementazioni specifiche, sia Dukee Juggy's azioni verrà eseguito.

Il metodo di sovraccarico è polimorfismo?

Molti programmatori sono confusi sulla relazione tra polimorfismo e override del metodo e sovraccarico del metodo. In effetti, l'unico metodo che prevale è il vero polimorfismo. L'overload condivide il nome dello stesso metodo ma i parametri sono diversi. Il polimorfismo è un termine ampio, quindi ci saranno sempre discussioni su questo argomento.

Qual è lo scopo del polimorfismo?

Il grande vantaggio e scopo dell'utilizzo del polimorfismo è quello di separare la classe client dal codice di implementazione. Invece di essere hardcoded, la classe client riceve l'implementazione per eseguire l'azione necessaria. In questo modo, la classe client sa quanto basta per eseguire le sue azioni, che è un esempio di accoppiamento libero.

Per comprendere meglio lo scopo del polimorfismo, dai un'occhiata a 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(); } } 

In questo esempio, puoi vedere che la SweetCreatorclasse conosce solo la  SweetProducer classe. Non conosce l'implementazione di ciascuno Sweet. Questa separazione ci dà flessibilità per aggiornare e riutilizzare le nostre classi e rende il codice molto più facile da mantenere. Quando si progetta il codice, cercare sempre modi per renderlo il più flessibile e gestibile possibile. il polimorfismo è una tecnica molto potente da utilizzare per questi scopi.

Suggerimento : l' @Overrideannotazione obbliga il programmatore a utilizzare la stessa firma del metodo che deve essere sovrascritta. Se il metodo non viene sovrascritto, si verificherà un errore di compilazione.

Tipi restituiti covarianti nella sostituzione del metodo

È possibile modificare il tipo restituito di un metodo sottoposto a override se si tratta di un tipo covariante. Un tipo covariante è fondamentalmente una sottoclasse del tipo restituito. Considera un esempio:

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

Poiché Dukeè a JavaMascot, siamo in grado di modificare il tipo di ritorno durante l'override.

Polimorfismo con le classi principali di Java

Usiamo il polimorfismo tutto il tempo nelle classi Java principali. Un esempio molto semplice è quando istanziamo la ArrayListclasse che dichiara l'   Listinterfaccia come un tipo:

 List list = new ArrayList(); 

Per andare oltre, considera questo esempio di codice utilizzando l'API Java Collections senza polimorfismo:

 public class ListActionWithoutPolymorphism { // Example without polymorphism void executeVectorActions(Vector vector) {/* Code repetition here*/} void executeArrayListActions(ArrayList arrayList) {/*Code repetition here*/} void executeLinkedListActions(LinkedList linkedList) {/* Code repetition here*/} void executeCopyOnWriteArrayListActions(CopyOnWriteArrayList copyOnWriteArrayList) { /* Code repetition here*/} } public class ListActionInvokerWithoutPolymorphism { listAction.executeVectorActions(new Vector()); listAction.executeArrayListActions(new ArrayList()); listAction.executeLinkedListActions(new LinkedList()); listAction.executeCopyOnWriteArrayListActions(new CopyOnWriteArrayList()); } 

Codice brutto, non è vero? Immagina di provare a mantenerlo! Ora guarda lo stesso esempio con il polimorfismo:

 public static void main(String … polymorphism) { ListAction listAction = new ListAction(); listAction.executeListActions(); } public class ListAction { void executeListActions(List list) { // Execute actions with different lists } } 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()); } } 

Il vantaggio del polimorfismo è la flessibilità e l'estensibilità. Invece di creare diversi metodi diversi, possiamo dichiarare solo un metodo che riceve il Listtipo generico .

Invocare metodi specifici in una chiamata a un metodo polimorfico

È possibile invocare metodi specifici in una chiamata polimorfica, ma farlo ha un costo in termini di flessibilità. Ecco un esempio:

 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()); } } 

La tecnica che stiamo usando qui è il casting o la modifica deliberata del tipo di oggetto in fase di esecuzione.

Si noti che è possibile richiamare un metodo specifico solo quando si esegue il cast del tipo generico nel tipo specifico. Una buona analogia sarebbe dire esplicitamente al compilatore: "Ehi, so cosa sto facendo qui, quindi eseguirò il cast dell'oggetto su un tipo specifico e userò un metodo specifico".  

Facendo riferimento all'esempio precedente, c'è una ragione importante per cui il compilatore rifiuta di accettare l'invocazione di un metodo specifico: la classe che viene passata potrebbe essere SolidSnake. In questo caso, non c'è modo per il compilatore di assicurarsi che ogni sottoclasse di MetalGearCharacterabbia il giveOrderToTheArmymetodo dichiarato.

La instanceofparola chiave riservata

Presta attenzione alla parola riservata instanceof. Prima di richiamare il metodo specifico che abbiamo chiesto se MetalGearCharacterè " instanceof" BigBoss. Se non è stato un BigBossesempio, avremmo ricevuto il seguente messaggio di eccezione:

 Exception in thread "main" java.lang.ClassCastException: com.javaworld.javachallengers.polymorphism.specificinvocation.SolidSnake cannot be cast to com.javaworld.javachallengers.polymorphism.specificinvocation.BigBoss 

La superparola chiave riservata

E se volessimo fare riferimento a un attributo o metodo da una superclasse Java? In questo caso potremmo usare la superparola riservata. Per esempio:

 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(); } } 

Using the reserved word super in Duke’s executeAction method  invokes the superclass method.  We then execute the specific action from Duke. That’s why we can see both messages in the output below:

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

Take the polymorphism challenge!

Let’s try out what you’ve learned about polymorphism and inheritance. In this challenge, you’re given a handful of methods from Matt Groening’s The Simpsons, and your challenge is to deduce what the output for each class will be. To start, analyze the following code carefully:

 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(); } } 

What do you think? What will the final output be? Don’t use an IDE to figure this out! The point is to improve your code analysis skills, so try to determine the output for yourself.

Choose your answer and you’ll be able to find the correct answer below.

 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 

What just happened? Understanding polymorphism

For the following method invocation:

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

the output will be “I love Sax!” This is  because we are passing a String to the method and Lisa has the method.

For the next invocation:

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

simpson.talk();

The output will be "Eat my shorts!" This is because we’re instantiating  the Simpson type with Bart.

Now check this one, which is a little trickier:

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

Here, we are using method overloading with inheritance. We are not passing anything to the talk method, which is why the Simpson talk method is invoked.  In this case the output will be:

 "Simpson!" 

Here’s one more:

 ((Bart) simpson).prank(); 

In this case, the prank String was passed when we instantiated the Bart class with new Bart("D'oh");. In this case,  first the super.prank method will be invoked, followed by the specific prank method from Bart. The output will be:

 "D'oh" "Knock Homer down" 

Video challenge! Debugging Java polymorphism and inheritance

Debugging is one of the easiest ways to fully absorb programming concepts while also improving your code. In this video you can follow along while I debug and explain the Java polymorphism challenge:

Common mistakes with polymorphism

It’s a common mistake to think it’s possible to invoke a specific method without using casting.

Another mistake is being unsure what method will be invoked when instantiating a class polymorphically. Remember that the method to be invoked is the method of the created instance.

Also remember that method overriding is not method overloading.

It’s impossible to override a method if the parameters are different. It is possible to change the return type of the overridden method if the return type is a subclass of the superclass method.

Cosa ricordare del polimorfismo

  • L'istanza creata determinerà quale metodo verrà richiamato quando si utilizza il polimorfismo.
  • L' @Overrideannotazione obbliga il programmatore a utilizzare un metodo sovrascritto; in caso contrario, ci sarà un errore del compilatore.
  • Il polimorfismo può essere utilizzato con classi normali, classi astratte e interfacce.
  • La maggior parte dei design pattern dipende da una qualche forma di polimorfismo.
  • L'unico modo per utilizzare un metodo specifico nella tua sottoclasse polimorfica è usare il casting.
  • È possibile progettare una struttura potente nel codice utilizzando il polimorfismo.
  • Esegui i tuoi test. In questo modo, sarai in grado di padroneggiare questo potente concetto!

Tasto di risposta

La risposta a questa sfidante Java è D . L'output sarebbe:

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

Questa storia, "Polimorfismo ed eredità in Java" è stata originariamente pubblicata da JavaWorld.