Polymorfism och arv i Java

Enligt legenden Venkat Subramaniam är polymorfism det viktigaste begreppet inom objektorienterad programmering. Polymorfism - eller förmågan hos ett objekt att utföra specialiserade åtgärder baserat på dess typ - är det som gör Java-koden flexibel. Designmönster som Command, Observer, Decorator, Strategy och många andra skapade av Gang Of Four, alla använder någon form av polymorfism. Att behärska detta koncept förbättrar din förmåga att tänka igenom lösningar på programmeringsutmaningar avsevärt.

Hämta koden

Du kan få källkoden för den här utmaningen och köra dina egna tester här: //github.com/rafadelnero/javaworld-challengers

Gränssnitt och arv i polymorfism

Med denna Java Challenger fokuserar vi på förhållandet mellan polymorfism och arv. Det viktigaste att komma ihåg är att polymorfism kräver arv eller gränssnittsimplementering . Du kan se detta i exemplet nedan med Duke och 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(); } } 

Utgången från den här koden kommer att vara:

 Punch! Fly! 

På grund av deras specifika implementeringar kommer båda Dukeoch dess Juggyåtgärder att utföras.

Är överbelastning av metod polymorfism?

Många programmerare är förvirrade över förhållandet mellan polymorfism och metodöverstyrning och metodöverbelastning. I själva verket är det enda metod som åsidosätter sann polymorfism. Överbelastning delar samma metods namn men parametrarna är olika. Polymorfism är en bred term, så det kommer alltid att finnas diskussioner om detta ämne.

Vad är syftet med polymorfism?

Den stora fördelen och syftet med att använda polymorfism är att koppla bort klientklassen från implementeringskoden. Istället för att vara hårdkodad får klientklassen implementeringen för att utföra nödvändiga åtgärder. På detta sätt vet klientklassen precis tillräckligt för att utföra sina handlingar, vilket är ett exempel på lös koppling.

För att bättre förstå syftet med polymorfism, ta en titt på 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(); } } 

I det här exemplet kan du se att SweetCreatorklassen bara känner till  SweetProducer klassen. Det känner inte till implementeringen av var och en Sweet. Den separationen ger oss flexibilitet att uppdatera och återanvända våra klasser, och det gör koden mycket lättare att underhålla. När du utformar din kod ska du alltid leta efter sätt att göra den så flexibel och underhållbar som möjligt. polymorfism är en mycket kraftfull teknik att använda för dessa ändamål.

Tips : @OverrideAnnotationen tvingar programmeraren att använda samma metodsignatur som måste åsidosättas. Om metoden inte åsidosätts kommer det att finnas ett kompileringsfel.

Kovarianta returtyper i åsidosättande av metod

Det är möjligt att ändra returtypen för en åsidosatt metod om det är en kovariant typ. En kovariant typ är i grunden en underklass av returtypen. Tänk på ett exempel:

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

Eftersom det Dukeär ett JavaMascot, kan vi ändra returtypen när vi åsidosätter.

Polymorfism med Java-klasserna

Vi använder polymorfism hela tiden i de centrala Java-klasserna. Ett mycket enkelt exempel är när vi instanserar ArrayListklassen som förklarar   Listgränssnittet som en typ:

 List list = new ArrayList(); 

För att gå längre, överväga detta kodexempel med hjälp av Java Collections API utan polymorfism:

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

Ful kod, eller hur? Tänk dig att försöka behålla det! Titta nu på samma exempel med polymorfism:

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

Fördelen med polymorfism är flexibilitet och töjbarhet. Istället för att skapa flera olika metoder kan vi bara deklarera en metod som tar emot den generiska Listtypen.

Anropa specifika metoder i ett polymorfiskt metodanrop

Det är möjligt att åberopa specifika metoder i ett polymorft samtal, men att göra det kostar flexibilitet. Här är ett exempel:

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

Tekniken vi använder här är gjutning eller medvetet ändring av objekttypen under körning.

Observera att det är möjligt att åberopa en specifik metod endast när du kastar den generiska typen till den specifika typen. En bra analogi skulle vara att uttryckligen säga till kompilatorn: "Hej, jag vet vad jag gör här, så jag kommer att kasta objektet till en specifik typ och använda en specifik metod."  

Med hänvisning till ovanstående exempel finns det en viktig anledning till att kompilatorn vägrar att acceptera specifik metodanrop: klassen som skickas kan vara SolidSnake. I det här fallet finns det inget sätt för kompilatorn att säkerställa att varje underklass MetalGearCharacterhar giveOrderToTheArmymetoden deklarerat.

Det instanceofreserverade nyckelordet

Var uppmärksam på det reserverade ordet instanceof. Innan vi åberopar den specifika metoden har vi frågat om det MetalGearCharacterär “ instanceofBigBoss. Om det inte var en BigBossinstans skulle vi få följande undantagsmeddelande:

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

Det superreserverade nyckelordet

Vad händer om vi vill hänvisa till ett attribut eller en metod från en Java-superklass? I det här fallet kan vi använda det superreserverade ordet. Till exempel:

 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.

Vad man ska komma ihåg om polymorfism

  • Den skapade instansen bestämmer vilken metod som ska åberopas när man använder polymorfism.
  • Den @Overrideannoteringen förpliktar programmeraren att använda ett åsidosatt metod; om inte, kommer det att finnas ett kompileringsfel.
  • Polymorfism kan användas med normala klasser, abstrakta klasser och gränssnitt.
  • De flesta designmönster beror på någon form av polymorfism.
  • Det enda sättet att använda en specifik metod i din polymorfa underklass är att använda gjutning.
  • Det är möjligt att utforma en kraftfull struktur i din kod med polymorfism.
  • Kör dina tester. Genom att göra detta kommer du att kunna behärska detta kraftfulla koncept!

Svarsknapp

Svaret på denna Java utmanare är D . Resultatet skulle vara:

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

Denna berättelse, "Polymorfism och arv i Java" publicerades ursprungligen av JavaWorld.