Java挑战者#3:多态性和继承

Java挑战者#3:多态性和继承


我们将继续翻译一系列有关Java问题的文章。 关于台词的最后一篇帖子引起了令人惊讶的激烈讨论 我们希望您也不会忽略本文。 是的-我们现在邀请您参加Java开发人员课程十周年纪念活动。


根据传说中的Venkat Subramaniam,多态性是面向对象编程中最重要的概念。 多态性 (即对象根据其类型执行特殊操作的能力)使Java代码具有灵活性。 由“四人帮”创建的设计模式(例如命令,观察员,装饰器,策略以及许多其他模式)都使用某种形式的多态性。 掌握此概念将大大提高您通过软件解决方案进行思考的能力。



您可以获取本文的源代码并在此处进行实验: https : //github.com/rafadelnero/javaworld-challengers


多态性的接口和继承


在本文中,我们将重点介绍多态与继承之间的关系。 要记住的主要事情是多态性需要接口的 继承实现 。 您可以在下面的Duke和Juggy例子中看到这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(); } } 

此代码的输出将如下所示:


 Punch! Fly! 

由于定义了特定的实现,因此将同时调用DukeJuggy方法。


方法是否重载了多态性? 许多程序员将多态与覆盖和重载关系混淆了。 实际上,只有重新定义方法才是真正的多态性。 重载使用相同的方法名称,但参数不同。 多态性是一个广义术语,因此将始终对此主题进行讨论。


多态性的目的是什么


使用多态性的一大优点和目的是减少与实现的客户端类连接。 客户端类代替了硬代码,而是获得了依赖关系的实现以执行必要的操作。 因此,客户端类知道完成其操作的最低要求,这是弱绑定的一个示例。


为了更好地理解多态的目的,请看一下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(); } } 

在此示例中,您可以看到SweetCreatorSweetCreator了解SweetProducer类。 他不知道每个Sweet的实现。 这种分离使我们可以灵活地更新和重用我们的类,这使得代码更易于维护。 设计代码时,请始终寻找使之尽可能灵活和方便的方法。 多态是用于此目的的一种非常有效的方法。


@Override要求程序员使用相同的方法签名,必须将其覆盖。 如果未重写该方法,则将出现编译错误。

重写方法时的协变量返回类型


如果它是协变类型 ,则可以更改重写方法的返回类型 。 协变类型基本上是返回值的子类。


考虑一个例子:


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

由于DukeJavaMascot ,因此我们可以在覆盖时更改返回值的类型。


Java基类中的多态


我们在基础Java类中不断使用多态。 一个非常简单的示例是使用类型声明作为List接口实例化ArrayList类。


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

考虑使用不带多态性的Java Collections API的示例代码:


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

令人讨厌的代码,对不对? 想象一下,您需要陪伴他! 现在考虑具有多态性的相同示例:


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

多态性的优点是灵活性和可扩展性。 无需创建几种不同的方法,我们可以声明一个获得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"); //      // 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 BigBoss MetalGearCharacter实例。 如果这不是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关键字


如果我们想从父类中引用属性或方法怎么办? 在这种情况下,我们可以使用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(); } } 

Duke类的executeAction方法中使用保留字super调用父类的方法。 然后,我们从Duke类执行特定的操作。 这就是为什么我们可以在输出中看到两条消息的原因:


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

解决多态性问题


让我们检查一下您从多态性和继承中学到的知识。


在此任务中,Matt Groening的《辛普森一家》为您提供了几种方法,从Wavs中您需要解开每个类的输出。 首先,仔细分析以下代码:


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

你觉得呢 结果如何? 不要使用IDE找出答案! 目标是提高您的代码分析技能,因此请自己决定。


选择答案(您可以在文章末尾找到正确的答案)。


A)
我爱萨克斯!
D'oh
辛普森!
D'oh


B)
萨克斯:)
吃我的短裤!
我爱萨克斯!
D'oh
击倒荷马


C)
萨克斯:)
D'oh
辛普森!
击倒荷马


D)
我爱萨克斯!
吃我的短裤!
辛普森!
D'oh
击倒荷马


发生什么事了 了解多态


对于以下方法调用:


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

输出将是“我爱萨克斯!”。 这是因为我们将字符串传递给方法,而Lisa类具有这种方法。


对于下一个电话:


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

输出将是“吃我的短裤!”。 这是因为我们用Bart初始化了Simpson类型。


现在看,这有点棘手:


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

在这里,我们使用继承的方法重载。 我们没有将任何内容传递给talk方法,因此调用了Simpsontalk方法。


在这种情况下,输出将为“ Simpson!”。


这是另一个:


 ((Bart) simpson).prank(); 

在这种情况下,在通过new Bart("D'oh");实例化Bart类时传递了prank字符串new Bart("D'oh"); 。 在这种情况下,首先调用super.prank()方法,然后再调用Bart类中的super.prank()方法。 结论将是:


 "D'oh" "Knock Homer down" 

常见的多态性错误


一个常见的错误是认为您可以在不使用类型强制转换的情况下调用特定方法。


另一个错误是不确定在多态实例化该类时将调用哪种方法。 请记住,被调用的方法是实例化实例的方法。


还请记住,覆盖方法并非方法的重载。


如果参数不同,则无法覆盖方法。 如果返回类型是子类,则可以更改重写方法的返回类型。


您需要记住的多态性


  • 创建的实例确定使用多态时将调用哪个方法。


  • @Override要求程序员使用覆盖的方法; 否则,将发生编译器错误。


  • 多态可以与常规类,抽象类和接口一起使用。


  • 大多数设计模式取决于某种形式的多态性。


  • 在多态子类中调用方法的唯一方法是使用类型转换。


  • 您可以使用多态来创建功能强大的代码结构。


  • 实验。 通过这个,您将能够掌握这个强大的概念!



答案


答案是D。


结论将是:


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

一如既往,我欢迎您的评论和问题。 我们正在公开课中等待Vitaly

Source: https://habr.com/ru/post/zh-CN429120/


All Articles