Java 继承与多态:概念、关键机制与容易踩的坑,一篇读懂
Java 继承与多态:概念、关键机制与容易踩的坑,一篇读懂
适合学过一遍快速抓重点。
写类的时候,总会遇到这样的问题:两个类看起来七八成相似,能不能把重复的部分抽出来?能不能让代码更灵活一些,而不是一堆 if-else 管理不同对象?继承与多态,正是为解决这些问题而设计的工具。
一、继承:把共性抽出来,让代码长得更干净
继承是面向对象里非常基础但极其重要的概念。它解决的核心问题只有一个:
把多个类中重复的代码抽取到一个更通用的父类中,实现复用。
于是:
- 子类获得父类的属性和方法;
- 子类只关注“新增”部分;
- 整个体系形成一种“is-a(是一个)”关系。
例如“狗是一种动物”、 “猫是一种动物”,就是典型继承关系。
1. 继承的写法
class 子类 extends 父类 {
// 自己新增的内容
}
子类会自动继承父类的成员变量和方法。
2. 成员访问的规则:就近原则
不论是访问变量还是方法,都遵循同一个原则:
自己有用自己的,没找到再去父类里找。
相比变量,方法还有一个额外情况:如果同名但参数列表不同,那属于重载,由参数类型决定调用哪个。
3. 与父类同名的成员:super 上场
如果子类和父类都有同名的字段或方法:
- 默认访问子类自己的;
- 想访问父类的,用
super.成员名。
例如:
super.a = 200;
super.methodB();
4. 子类构造方法:永远先构造父,再构造子
Java 规定:
创建子类对象时,必须先执行父类构造方法,再执行子类构造方法。
如果你不写 super(...),编译器也会自动加上 super()。
但要注意:
- 如果父类没有无参构造,你必须手动写
super(参数); super(...)必须是构造方法第一行;super(...)和this(...)不能同时出现,只能二选一。
5. 初始化顺序(继承情况下)
当父类和子类都存在静态代码块、实例代码块、构造器时,整体执行顺序:
- 父类静态代码块
- 子类静态代码块
- 父类实例代码块
- 父类构造方法
- 子类实例代码块
- 子类构造方法
静态内容只执行一次,但实例内容会在每次 new 时执行。
6. protected:让子类“看得见”
访问权限在继承关系里有所变化:
private:子类看不见(但确实继承了)。- default:子类必须在同包才能访问。
protected:不同包的子类也能访问。public:谁都能访问。
这是设计 API 时非常核心的决策点,关系到封装与扩展。
7. final:继承体系的“终点标志”
- 修饰类 → 不能被继承(如
String) - 修饰方法 → 不能被子类重写
- 修饰变量 → 常量
继承层次过深会让系统难维护,所以某些类被设计成 final,就是为了防止复杂继承。
8. 继承 vs 组合:别被继承迷住眼
继承是“is-a”,组合是“has-a”:
- 汽车 有 轮胎 → 组合
- 狗 是 动物 → 继承
如果只是复用行为,大多数场景组合比继承更灵活,不容易造成层次过深。
一般建议:能用组合尽量用组合。
二、多态:让同一段代码具备“根据对象改变行为”的能力
多态这个词,看起来玄,其实一句话就能解释:
父类引用指向子类对象,根据实际对象类型决定行为。
1. 多态成立的条件(三件套)
必须同时满足:
- 有继承关系
- 子类重写了父类的方法
- 通过父类引用调用该方法
例如:
Animal a = new Dog();
a.eat(); // 调用的是 Dog 重写的 eat()
2. 重写:外壳不变,核心重写
重写(override)的核心要求:
- 方法名相同
- 参数列表相同
- 返回值类型相同(或是协变类型)
- 访问权限不能更严格(如父类 public,子类不能写 protected)
- 不能重写 private、static、final、构造方法
建议始终写上 @Override 注解,能帮你避免拼写错误等低级 bug。
3. 方法绑定:重载靠编译期,重写靠运行期
- 重载(overload) = 静态绑定(编译期决定)
- 重写(override) = 动态绑定(运行期决定)
多态就是靠“动态绑定”实现的。
4. 向上转型:让代码更通用
把子类对象当成父类引用:
Animal a = new Cat();
好处:
- 能写出更通用、扩展性更强的代码
- 方法参数、返回值都可以统一成“父类型”
缺点:
- 父类引用看不到子类独有方法(比如不能调用
cat.mew())
5. 向下转型:小心使用!
如果需要访问子类独有方法,需要把父类引用转回子类类型:
Animal a = new Dog();
Dog d = (Dog)a; // OK
但如果类型不匹配,就会运行时抛异常:
Animal a = new Dog();
Cat c = (Cat)a; // 运行时 ClassCastException
因此通常与 instanceof 配合使用:
if (a instanceof Cat) {
Cat c = (Cat)a;
c.mew();
}
这个道理就是,把“狗”赋值给“动物”,是向上转型,因为狗本来就是诸多动物这个大集合中的一种。而把“动物”赋值给“狗”就是向下转型了,因为“动物”不一定是狗,就要加上强制转换的括号。
三、多态的好处与缺点
好处 1:减少 if-else,降低复杂度
没有多态时:
if (shape.equals("rect")) { ... }
else if (shape.equals("cycle")) { ... }
有了多态:
Shape[] shapes = {new Rect(), new Cycle(), ...};
for (Shape s : shapes) {
s.draw(); // 自动选择对应实现
}
代码更干净,也更容易扩展。
好处 2:扩展自然,不会破坏原代码
新增一个 Flower 类,只要保证继承自 Shape 并重写 draw(),调用代码完全不需要改动。
多态的缺陷
- 属性没有多态性
父类引用访问的永远是父类字段,不是子类的。 - 构造方法没有多态
动态绑定不作用于构造器。 - 构造器中使用可被重写的方法,会引发难查错误
例如:
class B {
public B() { func(); }
public void func() {}
}
class D extends B {
private int num = 1;
@Override
public void func() {
System.out.println(num); // num 还没初始化
}
}
输出可能是 0,而不是 1,因为子类尚未初始化。
因此建议 不要在构造方法里调用可被重写的方法。
四、继承与多态的关键易错点(高频考点)
你可以把这一节当作“复习清单”:
继承部分
- 子类构造方法一定会先执行父类构造方法
- 父类没有无参构造时,子类必须显式写
super(参数) super(...)必须是构造方法第一行super(...)与this(...)不能同时出现- 同名变量访问遵循就近原则
- 访问被隐藏的父类变量/方法需用
super private的成员虽然继承下来,但不可访问- default 成员不同包子类不可见
- protected 成员不同包子类可见
- 一个类能否继承另一个类也可能受
final限制
多态部分
- 多态必须基于继承 & 重写 & 父类引用
- static / private / final 方法无法重写,因此不具备多态性
- 字段没有多态性
- 向下转型一定要用
instanceof判断 - 重写的方法要求“方法签名一致+访问权限不更严格”
- 构造方法不要调用可被重写的方法
五、总结:继承与多态,别只是“会写”,要知道“为什么这样写”
继承让你减少重复代码,多态让你写出可扩展、可维护的结构。
但要记住,继承不是越多越好,多态也不是到处乱用。
真正的目标,是写出清晰、可扩展、安全的代码结构。
继承让你塑造类型体系,多态让你构建可扩展系统,而接口让整个系统摆脱继承链的束缚。
更多推荐



所有评论(0)