Polymorfisme en overerving in Java

Volgens de legende Venkat Subramaniam is polymorfisme het belangrijkste concept bij objectgeoriënteerd programmeren. Polymorfisme - of het vermogen van een object om gespecialiseerde acties uit te voeren op basis van het type - maakt Java-code flexibel. Ontwerppatronen zoals Command, Observer, Decorator, Strategy en vele anderen gemaakt door de Gang Of Four, gebruiken allemaal een vorm van polymorfisme. Het beheersen van dit concept verbetert uw vermogen om oplossingen voor programmeeruitdagingen te bedenken aanzienlijk.

Verkrijg de code

Je kunt de broncode voor deze uitdaging krijgen en je eigen tests hier uitvoeren: //github.com/rafadelnero/javaworld-challengers

Interfaces en overerving in polymorfisme

Met deze Java Challenger richten we ons op de relatie tussen polymorfisme en overerving. Het belangrijkste om in gedachten te houden is dat polymorfisme overerving of interface-implementatie vereist . Je kunt dit zien in het onderstaande voorbeeld, met Duke en 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(); } } 

De uitvoer van deze code is:

 Punch! Fly! 

Door hun specifieke toepassingen, zowel Dukeen Juggy's acties worden uitgevoerd.

Overbelast de methode polymorfisme?

Veel programmeurs zijn in de war over de relatie tussen polymorfisme en methode-overschrijving en methode-overbelasting. In feite is alleen het overschrijven van een methode echt polymorfisme. Overbelasting heeft dezelfde naam, maar de parameters zijn verschillend. Polymorfisme is een brede term, dus er zullen altijd discussies over dit onderwerp zijn.

Wat is het doel van polymorfisme?

Het grote voordeel en doel van het gebruik van polymorfisme is om de clientklasse los te koppelen van de implementatiecode. In plaats van hard-gecodeerd te zijn, ontvangt de cliëntklasse de implementatie om de nodige actie uit te voeren. Op deze manier weet de cliëntklasse net genoeg om zijn acties uit te voeren, wat een voorbeeld is van losse koppeling.

Om het doel van polymorfisme beter te begrijpen, bekijk de 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 dit voorbeeld kun je zien dat de SweetCreatorklas alleen de  SweetProducer klas kent . Het kent de implementatie van elk niet Sweet. Die scheiding geeft ons de flexibiliteit om onze klassen bij te werken en opnieuw te gebruiken, en het maakt de code veel gemakkelijker te onderhouden. Zoek bij het ontwerpen van uw code altijd naar manieren om deze zo flexibel en onderhoudbaar mogelijk te maken. polymorfisme is een zeer krachtige techniek om voor deze doeleinden te gebruiken.

Tip : de @Overrideannotatie verplicht de programmeur om dezelfde methodehandtekening te gebruiken die moet worden overschreven. Als de methode niet wordt overschreven, treedt er een compilatiefout op.

Covariante retourtypen bij het overschrijven van methoden

Het is mogelijk om het retourtype van een overschreven methode te wijzigen als het een covariant type is. Een covariant type is in feite een subklasse van het retourtype. Beschouw een voorbeeld:

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

Omdat Dukeis a JavaMascot, kunnen we het retourtype wijzigen bij overschrijven.

Polymorfisme met de kern Java-klassen

We gebruiken de hele tijd polymorfisme in de Java-kernklassen. Een heel eenvoudig voorbeeld is wanneer we de ArrayListklasse instantiëren en de Listinterface als een type declareren   :

 List list = new ArrayList(); 

Overweeg om verder te gaan dit codevoorbeeld met behulp van de Java Collections API zonder polymorfisme:

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

Lelijke code, is het niet? Stel je voor dat je het probeert te behouden! Bekijk nu hetzelfde voorbeeld met polymorfisme:

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

Het voordeel van polymorfisme is flexibiliteit en uitbreidbaarheid. In plaats van verschillende methoden te maken, kunnen we slechts één methode declareren die het generieke Listtype ontvangt .

Het aanroepen van specifieke methoden in een polymorfe methode-aanroep

Het is mogelijk om specifieke methoden in een polymorfe oproep aan te roepen, maar dit gaat ten koste van flexibiliteit. Hier is een voorbeeld:

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

De techniek die we hier gebruiken, is casten of het opzettelijk wijzigen van het objecttype tijdens runtime.

Merk op dat het mogelijk is om een specifieke methode te roepen alleen bij het gieten van de generieke type om het type specifiek. Een goede analogie zou zijn om expliciet tegen de compiler te zeggen: "Hé, ik weet wat ik hier doe, dus ik ga het object naar een specifiek type casten en een specifieke methode gebruiken."  

Verwijzend naar het bovenstaande voorbeeld, is er een belangrijke reden waarom de compiler weigert een specifieke methode-aanroep te accepteren: de klasse die wordt doorgegeven zou dat kunnen zijn SolidSnake. In dit geval is er geen manier voor de compiler om ervoor te zorgen dat elke subklasse MetalGearCharacterde giveOrderToTheArmymethode heeft gedeclareerd.

Het instanceofgereserveerde trefwoord

Let op het gereserveerde woord instanceof. Voordat we de specifieke methode aanroepen, hebben we gevraagd of deze MetalGearCharacter" instanceof" is BigBoss. Als het was niet een BigBossvoorbeeld, zouden we de volgende uitzondering bericht:

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

Het supergereserveerde trefwoord

Wat als we willen verwijzen naar een attribuut of methode uit een Java-superklasse? In dit geval kunnen we het supergereserveerde woord gebruiken. Bijvoorbeeld:

 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.

Wat te onthouden over polymorfisme

  • De gemaakte instantie zal bepalen welke methode wordt aangeroepen bij het gebruik van polymorfisme.
  • De @Overrideannotatie verplicht de programmeur om een ​​overschreven methode te gebruiken; zo niet, dan treedt er een compilatiefout op.
  • Polymorfisme kan worden gebruikt met normale klassen, abstracte klassen en interfaces.
  • De meeste ontwerppatronen zijn afhankelijk van een vorm van polymorfisme.
  • De enige manier om een ​​specifieke methode in uw polymorfe subklasse te gebruiken, is door gieten te gebruiken.
  • Het is mogelijk om een ​​krachtige structuur in uw code te ontwerpen met behulp van polymorfisme.
  • Voer uw tests uit. Door dit te doen, zul je dit krachtige concept onder de knie krijgen!

Antwoord sleutel

Het antwoord op deze Java uitdager is D . De output zou zijn:

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

Dit verhaal, "Polymorfisme en overerving in Java" werd oorspronkelijk gepubliceerd door JavaWorld.