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 Duke
e 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 SweetCreator
classe 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' @Override
annotazione 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 ArrayList
classe che dichiara l' List
interfaccia 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 List
tipo 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 MetalGearCharacter
abbia il giveOrderToTheArmy
metodo dichiarato.
La instanceof
parola 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 BigBoss
esempio, 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 super
parola chiave riservata
E se volessimo fare riferimento a un attributo o metodo da una superclasse Java? In questo caso potremmo usare la super
parola 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'
@Override
annotazione 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.