Java中的多态与继承
Java中的多态与继承
开始学习Java中的多态及如何在多态方法调用中进行方法调用
多态——即对象根据其类型执行特定操作的能力——是Java代码灵活性的核心。四人组(Gang Of Four)创建的许多设计模式都依赖于某种形式的多态,包括命令模式。本文将介绍Java多态的基础知识及如何在程序中使用它。
关于Java多态需要了解的内容
- 多态与Java继承
- 为何多态重要
- 方法重写中的多态
- 核心Java类中的多态
- 多态方法调用与类型转换
- 保留关键字与多态
- 多态的常见错误
- 关于多态需要记住的要点
多态与Java继承
我们将重点探讨多态与Java继承的关系。需记住的核心点是:多态需要继承或接口实现。以下示例通过Duke和Juggy展示这一点:
public abstract class JavaMascot {public abstract void executeAction();
}public class Duke extends JavaMascot {@Overridepublic void executeAction() {System.out.println("Punch!");}
}public class Juggy extends JavaMascot {@Overridepublic 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 {@Overridepublic void produceSweet() {System.out.println("Cake produced");}
}public class ChocolateProducer extends SweetProducer {@Overridepublic void produceSweet() {System.out.println("Chocolate produced");}
}public class CookieProducer extends SweetProducer {@Overridepublic 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
类仅知晓SweetProducer
类,而不了解每个甜点的具体实现。这种分离使类能灵活更新和重用,并大幅提升代码可维护性。设计代码时,应始终寻求使其尽可能灵活和可维护。多态是编写可重用Java代码的强力技术。
提示:@Override
注解强制程序员使用必须被重写的相同方法签名。若方法未被重写,将产生编译错误。
方法重载是多态吗?
许多程序员对多态与方法重写、重载的关系感到困惑。但只有方法重写是真正的多态。重载共享相同方法名但参数不同。多态是广义术语,因此相关讨论将持续存在。
方法重写中的多态
若返回类型是协变类型,则允许修改重写方法的返回类型。协变类型本质上是返回类型的子类。示例如下:
public abstract class JavaMascot {abstract JavaMascot getMascot();
}public class Duke extends JavaMascot {@OverrideDuke getMascot() {return new Duke();}
}
由于Duke
是JavaMascot
的子类,我们可在重写时修改返回类型。
核心Java类中的多态
我们在核心Java类中频繁使用多态。一个简单示例是实例化ArrayList
类时声明List
接口为类型:
List<String> list = new ArrayList<>();
进一步观察以下未使用多态的Java集合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 {@Overridevoid 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());}
}
此处使用的技术是类型转换(casting),即在运行时显式改变对象类型。
注意:只有将通用类型强制转换为具体类型后,才能调用特定方法。这相当于明确告诉编译器:“我知道自己在做什么,因此要将对象转换为具体类型并使用特定方法。”
在上述示例中,编译器拒绝接受特定方法调用的原因很重要:传入的类可能是SolidSnake
。在此情况下,编译器无法确保每个MetalGearCharacter
的子类都声明了giveOrderToTheArmy
方法。
保留关键字
注意保留字instanceof
。在调用特定方法前,我们需检查MetalGearCharacter
是否为BigBoss
的实例。若非BigBoss
实例,将收到以下异常信息:
Exception in thread "main" java.lang.ClassCastException: com.javaworld.javachallengers.polymorphism.specificinvocation.SolidSnake cannot be cast to com.javaworld.javachallengers.polymorphism.specificinvocation.BigBoss
若需引用Java超类的属性或方法,可使用保留字super
。例如:
public class JavaMascot {void executeAction() {System.out.println("The Java Mascot is about to execute an action!");}
}
public class Duke extends JavaMascot {@Overridevoid 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!
多态的常见错误
- 常见错误是认为无需类型转换即可调用特定方法。
- 另一个错误是在多态实例化类时不确认将调用哪个方法。需记住:被调用的方法是所创建实例的方法。
- 还需注意方法重写不同于方法重载。
- 若参数不同,则无法重写方法。若返回类型是超类方法的子类,则可以修改重写方法的返回类型。
关于多态需要记住的要点
- 所创建的实例将决定使用多态时调用哪个方法。
@Override
注解强制程序员使用重写方法;否则将产生编译错误。- 多态可用于普通类、抽象类和接口。
- 大多数设计模式依赖某种形式的多态。
- 调用多态子类中特定方法的唯一方式是使用类型转换。
- 可通过多态设计强大的代码结构。
接受Java多态挑战!
让我们测试你对多态和继承的理解。在此挑战中,你需要根据Matt Groening的辛普森一家代码推断每个类的输出。首先仔细分析以下代码:
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)
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
解答挑战
对于以下方法调用:
new Lisa().talk("Sax :)");
输出为“I love Sax!”,因为我们向方法传递了字符串且Lisa类有此方法。
下一调用:
Simpson simpson = new Bart("D'oh");
simpson.talk();
输出为“Eat my shorts!”,因为我们用Bart实例化了Simpson类型。
以下调用较为复杂:
Lisa lisa = new Lisa();
lisa.talk();
此处通过继承使用了方法重载。由于未向talk
方法传递参数,因此调用Simpson
的talk
方法,输出为:
"Simpson!"
最后一个调用:
((Bart) simpson).prank();
此例中,prank
字符串在实例化Bart时通过new Bart("D'oh")
传入。此时首先调用super.prank
方法,再执行Bart的特定prank
方法。输出为:
"D'oh"
"Knock Homer down"
因此正确答案是D。输出为:
I love Sax!
Eat my shorts!
Simpson!
D'oh
Knock Homer down
【注】本文译自:Polymorphism and inheritance in Java | InfoWorld