多态让”同一个父类引用,装不同子类对象,表现出不同行为”,是面向对象扩展性的核心。本篇拆解多态三要素、”编译看左边、运行看右边”的含义、转型与 instanceof 保护,再讲抽象类与接口的设计意图,并用模板方法模式把它们串起来。

本篇目标

  • 记住多态三要素,并理解每一条的必要性
  • 说清多态下成员变量、成员方法、静态方法的访问规则
  • 会用向上转型与 instanceof + 向下转型
  • 分清抽象类与接口的设计意图:is-a 与 like-a
  • 初识模板方法模式

多态三要素

多态即事物存在的多种形态,代码上体现为 Animal a = new Cat();。成立需要三个条件,缺一不可:要有继承关系(没有父类就没有”左边”);要有方法重写(不重写,子类没有新行为,多态毫无意义);要有父类引用指向子类对象(多态的书写形式本身)。

编译看左边,运行看右边

多态下的访问规则分三类。成员变量:编译看左边(父类得有这字段),运行也看左边(拿到的是父类字段值,变量不参与多态)。成员方法:编译看左边(父类必须有方法声明,否则编译失败),运行看右边(实际执行子类重写后的版本,即动态绑定)。静态方法:编译运行都看左边(静态属于类,不构成重写)。一句话:只有非静态成员方法是”编译看左边、运行看右边”。

这条规则解释了经典考题:Fu f = new Zi(); f.method(); 为何编译报错——父类 Fu 没有 method() 声明,左边这关就过不去。再如 B b = new C(); b.show(); 中若 show 内部又调了被 C 重写的 show2(),动态绑定依然生效,输出的是 C 的版本。

向上转型与向下转型

Animal a = new Cat(); 是向上转型,自动完成,代价是丢了子类特有功能——a 调不了 catchMouse()。想用回特有功能需向下转型:Cat c = (Cat) a;,前提是堆中对象真的是 Cat,否则运行期抛 ClassCastException。安全姿势是先 instanceof 判断:

if (a instanceof Cat) {
Cat c = (Cat) a;
c.catchMouse();
}

顺带一提:Java 16 起支持模式匹配,if (a instanceof Cat c) { c.catchMouse(); } 判断和转型一步完成,是现代写法的简化。

多态的好处与弊端

好处:提高维护性(继承保证)、提高扩展性(多态保证)。最直观的体现是方法形参写成父类:

public static void feed(Animal a) { a.eat(); }

传 Cat 是猫吃、传 Dog 是狗吃,未来新增 Pig 不用改 feed 一行代码。弊端也很直接:父类引用用不了子类特有行为,只能靠向下转型找补。

抽象类:看不懂但必须存在的父类

动物类的 eat() 怎么写?”动物吃饭”没有具体动作,说得清实现的是猫吃鱼、狗啃骨头。这种”看得懂声明、看不懂实现”的方法就该定义成抽象方法 public abstract void eat();,含抽象方法的类必须是抽象类 abstract class Animal。规则:抽象类不能 new,靠多态由具体子类实例化;子类要么也是抽象类,要么重写全部抽象方法;抽象类可以没有抽象方法(目的仅仅是禁止别人 new 它);抽象类里成员变量、构造方法、普通方法都能有,构造方法留给子类 super() 初始化用。

abstract 不能和 final、private、static 共存:final 禁重写、private 继承不到、static 无对象可绑定,都与”强制子类重写”矛盾。抽象类的两类方法各司其职:抽象方法强制子类去做,非抽象方法让子类免费继承——约束与复用,一个类里全齐。

接口:对外提供的规则

接口是更纯粹的”规范”:interface 定义、implements 实现、不能实例化、按多态方式使用。传统接口的成员非常克制:变量默认 public static final(只能是常量),方法默认 public abstract(只能是抽象方法),没有构造方法。一个类可以在继承一个类的同时实现多个接口,接口之间还能多继承——Java 用接口绕开了类单继承的限制。

抽象类 vs 接口:设计意图不同

维度 抽象类 接口
关系 is-a,继承体系的共性 like-a,体系外的扩展能力
成员变量 变量、常量皆可 只有 public static final 常量
成员方法 抽象与非抽象皆可 传统上只有抽象方法
构造方法 有(给子类初始化用) 无
数量 单继承 多实现,接口间可多继承

设计理念:抽象类定义”这个体系是什么”,接口定义”体系之外还能干什么”。猫狗都要吃饭——进抽象类 Animal;只有部分动物会跳高——做成接口 Jumpping,谁需要谁 implements,不强塞给整个体系。时代演进要知道:Java 8 起接口可以有 default 和 static 方法,Java 9 起允许 private 方法,两者能力差距在缩小,但”单继承 vs 多实现””有状态 vs 无状态”的边界仍在。

模板方法模式初识

抽象类最经典的应用:父类把算法骨架写在 final 方法里,把可变步骤留给子类重写。

abstract class Exam {
public final void takeExam() { // 骨架固定,禁止重写
question();
answer(); // 每个人的答案不同
}
public void question() { System.out.println("同一份试卷"); }
public abstract void answer(); // 交给子类
}

new 出不同学生,takeExam() 流程完全一致,answer() 各不相同——这就是”好莱坞原则”:别来找我们,我们会去找你。后来的 Servlet、Spring 大量模板都建立在这个思想上。

经典练习:猫狗跳高

动物抽象类:姓名、年龄、抽象 eat();猫狗继承并重写 eat();跳高并非人人都有,定义接口 Jumpping(jump() 方法),让”参加培训”的猫实现它。测试类用 Animal a = new Cat(); 体会多态,再用 instanceof + 向下转型调 jump()——三要素、转型、抽象类、接口一次练全。

小结

  • 多态 = 继承 + 重写 + 父类引用指向子类对象;只有非静态成员方法”编译看左边、运行看右边”。
  • 向上转型自动但丢特性,向下转型先用 instanceof 保护(Java 16+ 可用模式匹配)。
  • 抽象类表达 is-a 的体系共性,可带状态与实现;接口表达 like-a 的扩展能力,可多实现。
  • 模板方法模式:父类定骨架、子类填细节,是抽象类的杀手级应用。