Polimorfizm i dziedziczenie w Javie

Według legendy Venkata Subramaniama polimorfizm jest najważniejszą koncepcją w programowaniu obiektowym. Polimorfizm - czyli zdolność obiektu do wykonywania wyspecjalizowanych działań w oparciu o jego typ - jest tym, co czyni kod Java elastycznym. Wzorce projektowe, takie jak Command, Observer, Decorator, Strategy i wiele innych stworzonych przez Gang Of Four, wykorzystują pewną formę polimorfizmu. Opanowanie tej koncepcji znacznie poprawia zdolność przemyślenia rozwiązań wyzwań programistycznych.

Zdobądź kod

Możesz pobrać kod źródłowy tego wyzwania i przeprowadzić własne testy tutaj: //github.com/rafadelnero/javaworld-challengers

Interfejsy i dziedziczenie w polimorfizmie

W tym programie Java Challenger skupiamy się na związku między polimorfizmem a dziedziczeniem. Najważniejszą rzeczą, o której należy pamiętać, jest to, że polimorfizm wymaga dziedziczenia lub implementacji interfejsu . Możesz to zobaczyć na poniższym przykładzie, przedstawiającym Duke'a i Juggy'ego:

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

Wynik tego kodu będzie wyglądał następująco:

 Punch! Fly! 

Ze względu na ich specyficzne wdrożeń, zarówno Dukei Juggydziałań „s zostanie wykonane.

Czy metoda przeciąża polimorfizm?

Wielu programistów jest zdezorientowanych co do związku polimorfizmu z nadpisywaniem metody i jej przeciążaniem. W rzeczywistości jedynym nadpisywaniem metody jest prawdziwy polimorfizm. Przeciążanie ma tę samą nazwę metody, ale parametry są różne. Polimorfizm to pojęcie szerokie, więc zawsze będą toczyć się dyskusje na ten temat.

Jaki jest cel polimorfizmu?

Ogromną zaletą i celem stosowania polimorfizmu jest oddzielenie klasy klienta od kodu implementacji. Klasa klienta zamiast być zakodowana na stałe otrzymuje implementację w celu wykonania niezbędnej akcji. W ten sposób klasa klienta wie tylko tyle, aby wykonać swoje akcje, co jest przykładem luźnego sprzężenia.

Aby lepiej zrozumieć cel polimorfizmu, spójrz na 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(); } } 

W tym przykładzie widać, że SweetCreatorklasa zna tylko tę  SweetProducer klasę. Nie zna implementacji każdego Sweet. Ta separacja zapewnia nam elastyczność w aktualizowaniu i ponownym użyciu naszych klas, a także znacznie ułatwia utrzymanie kodu. Projektując kod, zawsze szukaj sposobów, aby uczynić go tak elastycznym i łatwym w utrzymaniu, jak to tylko możliwe. polimorfizm jest bardzo skuteczną techniką do wykorzystania w tych celach.

Wskazówka : @OverrideAdnotacja zobowiązuje programistę do użycia tej samej sygnatury metody, która musi zostać przesłonięta. Jeśli metoda nie zostanie zastąpiona, wystąpi błąd kompilacji.

Kowariantne typy zwracane w zastępowaniu metody

Istnieje możliwość zmiany zwracanego typu zastępowanej metody, jeśli jest to typ kowariantny. Kowariantna typu jest w zasadzie podklasą typ zwracany. Rozważ przykład:

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

Ponieważ Dukejest a JavaMascot, jesteśmy w stanie zmienić zwracany typ podczas zastępowania.

Polimorfizm z podstawowymi klasami Javy

W klasach Javy cały czas używamy polimorfizmu. Bardzo prostym przykładem jest tworzenie instancji ArrayListklasy deklarującej   Listinterfejs jako typ:

 List list = new ArrayList(); 

Aby przejść dalej, rozważ ten przykład kodu przy użyciu Java Collections API bez polimorfizmu:

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

Brzydki kod, prawda? Wyobraź sobie, że próbujesz to utrzymać! Teraz spójrz na ten sam przykład z polimorfizmem:

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

The benefit of polymorphism is flexibility and extensibility. Instead of creating several different methods, we can declare just one method that receives the generic List type.

Invoking specific methods in a polymorphic method call

It's possible to invoke specific methods in a polymorphic call, but doing it comes at the cost of flexibility. Here’s an example:

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

The technique we’re using here is casting, or deliberately changing the object type at runtime.

Note that it’s possible to invoke a specific method only when casting the generic type to the specific type. A good analogy would be saying explicitly to the compiler, “Hey, I know what I’m doing here, so I’m going to cast the object to a specific type and use a specific method.”  

Referring to the above example, there is an important reason the compiler refuses to accept specific method invocation: the class that is being passed could be SolidSnake. In this case, there is no way for the compiler to ensure every subclass of MetalGearCharacter has the giveOrderToTheArmy method declared.

The instanceof reserved keyword

Pay attention to the reserved word instanceof. Before invoking the specific method we’ve asked if MetalGearCharacter is “instanceofBigBoss. If it wasn’t a BigBoss instance, we would receive the following exception message:

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

The super reserved keyword

What if we wanted to reference an attribute or method from a Java superclass? In this case we could use the super reserved word. For example:

 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.

O czym należy pamiętać o polimorfizmie

  • Utworzona instancja określi, jaka metoda zostanie wywołana przy użyciu polimorfizmu.
  • @OverrideAdnotacja obliguje programator do użycia metody zniesione; jeśli nie, wystąpi błąd kompilatora.
  • Polimorfizmu można używać w przypadku klas normalnych, klas abstrakcyjnych i interfejsów.
  • Większość wzorców projektowych zależy od jakiejś formy polimorfizmu.
  • Jedynym sposobem użycia określonej metody w podklasie polimorficznej jest użycie rzutowania.
  • Możliwe jest zaprojektowanie potężnej struktury w kodzie przy użyciu polimorfizmu.
  • Przeprowadź testy. Robiąc to, będziesz w stanie opanować tę potężną koncepcję!

Klucz odpowiedzi

Odpowiedź na to jest Java challenger D . Wynik byłby następujący:

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

Ta historia „Polimorfizm i dziedziczenie w Javie” została pierwotnie opublikowana przez JavaWorld.