Полиморфизм и наследование в Java

Согласно легенде Венкату Субраманиаму, полиморфизм - наиболее важное понятие в объектно-ориентированном программировании. Полиморфизм - или способность объекта выполнять специализированные действия в зависимости от его типа - это то, что делает код Java гибким. Такие шаблоны проектирования, как Command, Observer, Decorator, Strategy и многие другие, созданные Gang Of Four, используют ту или иную форму полиморфизма. Освоение этой концепции значительно улучшит вашу способность обдумывать решения проблем программирования.

Получить код

Вы можете получить исходный код для этой задачи и запустить свои собственные тесты здесь: //github.com/rafadelnero/javaworld-challengers

Интерфейсы и наследование в полиморфизме

В этом Java Challenger мы фокусируемся на взаимосвязи между полиморфизмом и наследованием. Главное помнить, что полиморфизм требует наследования или реализации интерфейса . Вы можете увидеть это в приведенном ниже примере с участием Герцога и Джагги:

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

Результатом этого кода будет:

 Punch! Fly! 

Из - за их конкретных реализаций, как Dukeи Juggy«s действия будут выполнены.

Является ли метод перегрузкой полиморфизма?

Многие программисты не понимают отношения полиморфизма к переопределению метода и перегрузке метода. Фактически, истинным полиморфизмом является только переопределение метода. Перегрузка использует то же имя метода, но другие параметры. Полиморфизм - это широкий термин, поэтому дискуссии по этой теме всегда будут.

Какая цель полиморфизма?

Большое преимущество и цель использования полиморфизма - отделить клиентский класс от кода реализации. Вместо того, чтобы быть жестко запрограммированным, клиентский класс получает реализацию для выполнения необходимого действия. Таким образом, клиентский класс знает достаточно, чтобы выполнить свои действия, что является примером слабой связи.

Чтобы лучше понять цель полиморфизма, взгляните на 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(); } } 

В этом примере вы можете видеть, что SweetCreatorкласс знает только  SweetProducer класс. Он не знает реализации каждого из них Sweet. Такое разделение дает нам гибкость для обновления и повторного использования наших классов, а также значительно упрощает обслуживание кода. При разработке кода всегда ищите способы сделать его максимально гибким и поддерживаемым. полиморфизм - очень мощный метод, который можно использовать для этих целей.

Совет : @Overrideаннотация обязывает программиста использовать ту же сигнатуру метода, которую необходимо переопределить. Если метод не переопределен, произойдет ошибка компиляции.

Ковариантные возвращаемые типы при переопределении метода

Можно изменить тип возвращаемого значения переопределенного метода, если это ковариантный тип. Ковариантны типа в основном подкласс возвращаемого типа. Рассмотрим пример:

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

Поскольку Dukeэто a JavaMascot, мы можем изменить тип возвращаемого значения при переопределении.

Полиморфизм с основными классами Java

Мы постоянно используем полиморфизм в основных классах Java. Один очень простой пример - это когда мы создаем экземпляр ArrayListкласса, объявляя   Listинтерфейс как тип:

 List list = new ArrayList(); 

Чтобы пойти дальше, рассмотрим этот пример кода с использованием API коллекций Java без полиморфизма:

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

Уродливый код, не правда ли? Представьте себе попытку сохранить это! Теперь посмотрим на тот же пример с полиморфизмом:

 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.

Что нужно помнить о полиморфизме

  • Созданный экземпляр будет определять, какой метод будет вызываться при использовании полиморфизма.
  • @OverrideАннотации обязывает программиста использовать перекрытый метод; в противном случае будет ошибка компилятора.
  • Полиморфизм можно использовать с обычными классами, абстрактными классами и интерфейсами.
  • Большинство шаблонов проектирования зависят от той или иной формы полиморфизма.
  • Единственный способ использовать определенный метод в вашем полиморфном подклассе - использовать приведение типов.
  • Используя полиморфизм, можно создать мощную структуру в вашем коде.
  • Проведите свои тесты. Сделав это, вы сможете освоить эту мощную концепцию!

Ключ ответа

Ответ на этот Java претендентом является D . Результатом будет:

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

Этот рассказ «Полиморфизм и наследование в Java» был первоначально опубликован JavaWorld.