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

Според легендата Venkat Subramaniam, полиморфизмът е най-важното понятие в обектно-ориентираното програмиране. Полиморфизмът - или способността на даден обект да изпълнява специализирани действия въз основа на неговия тип - е това, което прави 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"Действията ще бъде изпълнена.

Методът претоварва ли полиморфизма?

Много програмисти са объркани относно връзката на полиморфизма с отменянето на метода и претоварването на метода. Всъщност единственият метод, който отменя, е истинският полиморфизъм. Претоварването споделя името на същия метод, но параметрите са различни. Полиморфизмът е широко понятие, така че винаги ще има дискусии по тази тема.

Каква е целта на полиморфизма?

Голямото предимство и цел на използването на полиморфизма е да се отдели клиентският клас от кода за изпълнение. Вместо да е трудно кодиран, клиентският клас получава изпълнението, за да изпълни необходимото действие. По този начин клиентският клас знае точно достатъчно, за да изпълни своите действия, което е пример за свободно свързване.

За да разберете по-добре целта на полиморфизма, разгледайте 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Анотацията задължава програмиста да използва същия подпис на метода, който трябва да бъде заменен. Ако методът не е заменен, ще има грешка при компилацията.

Ковариантни типове на връщане при заместване на метода

Възможно е да промените типа на връщане на заменен метод, ако той е ковариативен тип. А covariant тип е основно подклас на типа на замяна. Помислете за пример:

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

За да продължите по-нататък, помислете за този пример на кода, използвайки Java Collections API без полиморфизъм:

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

Ползата от полиморфизма е гъвкавостта и разтегливостта. Вместо да създаваме няколко различни метода, можем да декларираме само един метод, който получава общия Listтип.

Извикване на специфични методи в повикване на полиморфен метод

Възможно е да се извикат конкретни методи в полиморфно повикване, но това се прави с цената на гъвкавост. Ето пример:

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

Техниката, която използваме тук, е преливане или умишлено промяна на типа обект по време на изпълнение.

Имайте предвид, че е възможно да се извика конкретен метод само при прехвърляне на родовия тип към конкретния тип. Добра аналогия би била да се каже изрично на компилатора: „Хей, знам какво правя тук, така че ще прехвърля обекта към определен тип и ще използвам специфичен метод.“  

Позовавайки се на горния пример, има важна причина, поради която компилаторът отказва да приеме извикване на конкретен метод: класът, който се предава, може да бъде SolidSnake. В този случай няма начин компилаторът да гарантира, че всеки подклас на MetalGearCharacterима giveOrderToTheArmyдеклариран метод.

Най- instanceofрезервиран ключова дума

Обърнете внимание на запазената дума instanceof. Преди да извикаме конкретния метод, ние попитахме дали MetalGearCharacterе „ instanceofBigBoss. Ако не беше един BigBossпример, ние ще се появи следното съобщение изключение:

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

Най- superрезервиран ключова дума

Ами ако искаме да се позовем на атрибут или метод от суперклас на Java? В този случай бихме могли да използваме superзапазената дума. Например:

 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.