Java 多态、抽象类与接口
多态让”同一个父类引用,装不同子类对象,表现出不同行为”,是面向对象扩展性的核心。本篇拆解多态三要素、”编译看左边、运行看右边”的含义、转型与 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) { |
顺带一提: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 { |
new 出不同学生,takeExam() 流程完全一致,answer() 各不相同——这就是”好莱坞原则”:别来找我们,我们会去找你。后来的 Servlet、Spring 大量模板都建立在这个思想上。
经典练习:猫狗跳高
动物抽象类:姓名、年龄、抽象 eat();猫狗继承并重写 eat();跳高并非人人都有,定义接口 Jumpping(jump() 方法),让”参加培训”的猫实现它。测试类用 Animal a = new Cat(); 体会多态,再用 instanceof + 向下转型调 jump()——三要素、转型、抽象类、接口一次练全。
小结
- 多态 = 继承 + 重写 + 父类引用指向子类对象;只有非静态成员方法”编译看左边、运行看右边”。
- 向上转型自动但丢特性,向下转型先用 instanceof 保护(Java 16+ 可用模式匹配)。
- 抽象类表达 is-a 的体系共性,可带状态与实现;接口表达 like-a 的扩展能力,可多实现。
- 模板方法模式:父类定骨架、子类填细节,是抽象类的杀手级应用。

