写点什么

Java 中的多态与继承

作者:码语者
  • 2025-04-30
    上海
  • 本文字数:5190 字

    阅读完需:约 17 分钟

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(); }}
复制代码


代码输出为:


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类仅知晓SweetProducer类,而不了解每个甜点的具体实现。这种分离使类能灵活更新和重用,并大幅提升代码可维护性。设计代码时,应始终寻求使其尽可能灵活和可维护。多态是编写可重用 Java 代码的强力技术。


提示@Override注解强制程序员使用必须被重写的相同方法签名。若方法未被重写,将产生编译错误。

方法重载是多态吗?

许多程序员对多态与方法重写、重载的关系感到困惑。但只有方法重写是真正的多态。重载共享相同方法名但参数不同。多态是广义术语,因此相关讨论将持续存在。

方法重写中的多态

若返回类型是协变类型,则允许修改重写方法的返回类型。协变类型本质上是返回类型的子类。示例如下:


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


由于DukeJavaMascot的子类,我们可在重写时修改返回类型。

核心 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!
复制代码

多态的常见错误

  • 常见错误是认为无需类型转换即可调用特定方法。

  • 另一个错误是在多态实例化类时不确认将调用哪个方法。需记住:被调用的方法是所创建实例的方法。

  • 还需注意方法重写不同于方法重载

  • 若参数不同,则无法重写方法。若返回类型是超类方法的子类,则可以修改重写方法的返回类型。

关于多态需要记住的要点

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

  • @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方法传递参数,因此调用Simpsontalk方法,输出为:


"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'ohKnock Homer down
复制代码




【注】本文译自:[Polymorphism and inheritance in Java | InfoWorld ](Polymorphism and inheritance in Java | InfoWorld)


发布于: 刚刚阅读数: 4
用户头像

码语者

关注

分享程序人生。 2019-07-04 加入

“码”界老兵,分享程序人生。

评论

发布
暂无评论
Java中的多态与继承_Java_码语者_InfoQ写作社区