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 {
@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();
}
}
复制代码
代码输出为:
由于各自的具体实现,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
类仅知晓SweetProducer
类,而不了解每个甜点的具体实现。这种分离使类能灵活更新和重用,并大幅提升代码可维护性。设计代码时,应始终寻求使其尽可能灵活和可维护。多态是编写可重用 Java 代码的强力技术。
提示:@Override
注解强制程序员使用必须被重写的相同方法签名。若方法未被重写,将产生编译错误。
方法重载是多态吗?
许多程序员对多态与方法重写、重载的关系感到困惑。但只有方法重写是真正的多态。重载共享相同方法名但参数不同。多态是广义术语,因此相关讨论将持续存在。
方法重写中的多态
若返回类型是协变类型,则允许修改重写方法的返回类型。协变类型本质上是返回类型的子类。示例如下:
public abstract class JavaMascot {
abstract JavaMascot getMascot();
}
public class Duke extends JavaMascot {
@Override
Duke 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 {
@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());
}
}
复制代码
此处使用的技术是类型转换(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 {
@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!
复制代码
多态的常见错误
关于多态需要记住的要点
接受 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
方法,输出为:
最后一个调用:
((Bart) simpson).prank();
复制代码
此例中,prank
字符串在实例化 Bart 时通过new Bart("D'oh")
传入。此时首先调用super.prank
方法,再执行 Bart 的特定prank
方法。输出为:
因此正确答案是 D。输出为:
I love Sax!
Eat my shorts!
Simpson!
D'oh
Knock Homer down
复制代码
【注】本文译自:[Polymorphism and inheritance in Java | InfoWorld ](Polymorphism and inheritance in Java | InfoWorld)
评论