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!
由于定义了特定的实现,因此将同时调用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> 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(); } }
在此示例中,您可以看到SweetCreator
类SweetCreator
了解SweetProducer
类。 他不知道每个Sweet
的实现。 这种分离使我们可以灵活地更新和重用我们的类,这使得代码更易于维护。 设计代码时,请始终寻找使之尽可能灵活和方便的方法。 多态是用于此目的的一种非常有效的方法。
@Override
要求程序员使用相同的方法签名,必须将其覆盖。 如果未重写该方法,则将出现编译错误。
重写方法时的协变量返回类型
如果它是协变类型 ,则可以更改重写方法的返回类型 。 协变类型基本上是返回值的子类。
考虑一个例子:
public abstract class JavaMascot { abstract JavaMascot getMascot(); } public class Duke extends JavaMascot { @Override Duke getMascot() { return new Duke(); } }
由于Duke
是JavaMascot
,因此我们可以在覆盖时更改返回值的类型。
Java基类中的多态
我们在基础Java类中不断使用多态。 一个非常简单的示例是使用类型声明作为List
接口实例化ArrayList
类。
List<String> list = new ArrayList<>();
考虑使用不带多态性的Java Collections API的示例代码:
public class ListActionWithoutPolymorphism {
令人讨厌的代码,对不对? 想象一下,您需要陪伴他! 现在考虑具有多态性的相同示例:
public static void main(String... polymorphism) { ListAction listAction = new ListAction(); listAction.executeListActions(); } public class ListAction { void executeListActions(List<Object> list) {
多态性的优点是灵活性和可扩展性。 无需创建几种不同的方法,我们可以声明一个获得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");
我们在这里使用的技术是在运行时强制转换或有意识地更改对象的类型。
请注意,仅当将更一般的类型转换为更具体的类型时,才可以调用特定方法。 一个很好的类比是明确告诉编译器:“嘿,我知道我在这里做什么,所以我将对象转换为特定类型,我将使用此方法。”
参考上面的示例,编译器有充分的理由不接受某些方法的调用:传递的类必须为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
方法,因此调用了Simpson
的talk
方法。
在这种情况下,输出将为“ Simpson!”。
这是另一个:
((Bart) simpson).prank();
在这种情况下,在通过new Bart("D'oh");
实例化Bart
类时传递了prank
字符串new Bart("D'oh");
。 在这种情况下,首先调用super.prank()
方法,然后再调用Bart
类中的super.prank()
方法。 结论将是:
"D'oh" "Knock Homer down"
常见的多态性错误
一个常见的错误是认为您可以在不使用类型强制转换的情况下调用特定方法。
另一个错误是不确定在多态实例化该类时将调用哪种方法。 请记住,被调用的方法是实例化实例的方法。
还请记住,覆盖方法并非方法的重载。
如果参数不同,则无法覆盖方法。 如果返回类型是子类,则可以更改重写方法的返回类型。
您需要记住的多态性
答案
答案是D。
结论将是:
I love Sax! Eat my shorts! Simpson! D'oh Knock Homer down
一如既往,我欢迎您的评论和问题。 我们正在公开课中等待Vitaly 。