📜 ⬆️ ⬇️

Java Challengers # 3: Polymorphism and Inheritance

Java Challengers # 3: Polymorphism and Inheritance


We continue to translate a series of articles with puzzles on Java. The last post about the line caused a surprisingly heated discussion . We hope that you will not pass by this article either. And yes - we now invite the tenth anniversary stream of our Java Developer course.


According to the legendary Venkat Subramaniam, polymorphism is the most important concept in object-oriented programming. Polymorphism — or the ability of an object to perform specialized actions based on its type — is what makes Java code flexible. Design patterns, such as Command (Command), Observer, Decorator, Strategy, and many others created by a gang of four, all use some form of polymorphism. Mastering this concept will greatly improve your ability to think through software solutions.



You can get the source code for this article and experiment here: https://github.com/rafadelnero/javaworld-challengers


Interfaces and inheritance in polymorphism


In this article, we will focus on the relationship between polymorphism and inheritance. The main thing to keep in mind is that polymorphism requires inheritance or interface implementation . You can see this in the example below with Duke and 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(); } } 

The output of this code will be as follows:


 Punch! Fly! 

Since the concrete implementations are defined, the methods of Duke and Juggy will be called.


Is overloading of the method a polymorphism? Many programmers confuse a polymorphism relationship with overriding methods and overloading methods . In fact, only redefinition of the method is true polymorphism. Overload uses the same method name, but different parameters. Polymorphism is a broad term, so there will always be discussions on this topic.


What is the purpose of polymorphism


The big advantage and purpose of using polymorphism is to reduce the client class relatedness with the implementation. Instead of hardcoding, the client class gets a dependency implementation to perform the necessary action. Thus, the client class knows a minimum to perform its actions, which is an example of weak binding.


To better understand the purpose of polymorphism, take a look at 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> sweetProducer; public SweetCreator(List<SweetProducer> 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 this example, you can see that the SweetCreator class knows only about the SweetProducer class. He does not know the realization of every Sweet . This separation gives us the flexibility to update and reuse our classes, and this makes the code much easier to maintain. When designing a code, always look for ways to make it as flexible and convenient as possible. Polymorphism is a very powerful way to use for this purpose.


The @Override obliges the programmer to use the same method signature that needs to be redefined. If the method is not overridden, there will be a compilation error.

Covariant return types on method redefinition


You can change the return type of the overridden method if it is a covariant type . The covariant type is basically a subclass of the return value.


Consider an example:


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

Since Duke is a JavaMascot , we can change the type of the return value during the override.


Polymorphism in Java Base Classes


We constantly use polymorphism in the base Java classes. One very simple example is to create an instance of the ArrayList class with a type declaration as the List interface.


 List<String> list = new ArrayList<>(); 

Consider sample code using the Java Collections API without polymorphism:


 public class ListActionWithoutPolymorphism { //    void executeVectorActions(Vector<Object> vector) {/*    */} void executeArrayListActions(ArrayList<Object> arrayList) {/*    */} void executeLinkedListActions(LinkedList<Object> linkedList) {/*    */} void executeCopyOnWriteArrayListActions(CopyOnWriteArrayList<Object> copyOnWriteArrayList) { /*    */} } public class ListActionInvokerWithoutPolymorphism { listAction.executeVectorActions(new Vector<>()); listAction.executeArrayListActions(new ArrayList<>()); listAction.executeLinkedListActions(new LinkedList<>()); listAction.executeCopyOnWriteArrayListActions(new CopyOnWriteArrayList<>()); } 

Disgusting code, isn't it? Imagine that you need to accompany him! Now consider the same example with polymorphism:


 public static void main(String... polymorphism) { ListAction listAction = new ListAction(); listAction.executeListActions(); } public class ListAction { void executeListActions(List<Object> list) { //      } } 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 advantage of polymorphism is flexibility and extensibility. Instead of creating several different methods, we can declare one method that gets the type List .


Calling specific methods for the polymorphic method


You can call specific methods in a polymorphic method call, this is due to flexibility. Here is 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"); //      // 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 use here is casting or consciously changing the type of an object at run time.


Note that calling a specific method is possible only when casting a more general type to a more specific type. A good analogy would be to tell the compiler explicitly: "Hey, I know what I'm doing here, so I'm going to cast the object to a specific type and will use this method."


Referring to the example above, the compiler has a good reason for not accepting calls to certain methods: the class that is passed must be SolidSnake . In this case, the compiler has no way to ensure that each MetalGearCharacter subclass has the giveOrderToTheArmy method.


Instanceof keyword


Notice the instanceof reserved word. Before calling a particular method, we asked if MetalGearCharacter instance (instance) of BigBoss . If this is not a BigBoss instance, we will get the following exception:


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

super keyword


What if we want to refer to an attribute or method from the parent class? In this case, we can use the super keyword.
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 the executeAction method of the Duke class calls the method of the parent class. Then we perform a specific action from the Duke class. That is why we can see both messages in the output:


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

Solve the problem of polymorphism


Let's check what you learned about polymorphism and inheritance.


In this task you are given several methods from Matt Groening's The Simpsons, Vavam is required to unravel what the output for each class will be. First, carefully review the following code:


 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 be the result? Do not use IDE to find out! The goal is to improve your code analysis skills, so try to solve it yourself.


Choose your answer (the correct answer can be found at the end of the article).


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 happened? Understanding polymorphism


For the next method call:


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

The output will be "I love Sax!". This is because we are passing a string to a method, and the Lisa class has such a method.


For the next call:


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

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


Now look, this is a bit more complicated:


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

Here we use method overloading with inheritance. We don't pass anything to the talk method, so the Simpson talk method is called.


In this case, the output will be "Simpson!".


Here is another one:


 ((Bart) simpson).prank(); 

In this case, the string prank was transferred when creating an instance of the Bart class via new Bart("D'oh"); . In this case, the super.prank() method is first called, and then the part method prank() from the Bart class. The output will be:


 "D'oh" "Knock Homer down" 

Common mistakes with polymorphism


A common mistake is to think that you can call a particular method without using a cast.


Another mistake is uncertainty about what method will be called upon polymorphic class instance creation. Remember that the method being called is a method of the created instance.


Also remember that overriding a method is not a method overload.


You cannot override a method if the parameters differ. You can change the return type of the overridden method if the return type is a subclass.


What you need to remember about polymorphism



Answer


The answer is D.


The output will be:


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

As always, I welcome your comments and questions. And we are waiting for Vitali in the open lesson .


')

Source: https://habr.com/ru/post/429120/


All Articles