面向对象:封装、继承与多态
更新时间:2026-09-20。封装、继承、多态是 Java OOP 的三大支柱。本篇聚焦继承与多态怎么落地、动态绑定在运行时如何分派,访问控制细节配合接口与抽象类阅读。
一、三大特征一张表
| 特征 | 关键字/手段 | 解决什么 |
|---|---|---|
| 封装 | private 字段 + getter/setter | 隐藏内部状态,校验入口,控制修改 |
| 继承 | extends(单继承) | 复用父类字段/方法,表达 is-a |
| 多态 | 父类引用 + @Override | 同一调用,实际对象不同则行为不同 |
二、封装:把状态保护起来
java
class Account {
private long balance; // 字段私有,外部不能直接改
public long getBalance() { return balance; }
public void deposit(long n) {
if (n <= 0) throw new IllegalArgumentException("金额必须为正");
balance += n; // 所有修改走方法,便于校验
}
}三、继承与方法重写
java
class Animal {
protected String name;
Animal(String name) { this.name = name; }
String sound() { return "..."; } // 父类方法
}
class Dog extends Animal {
Dog(String name) { super(name); } // 必须先调父类构造器
@Override
String sound() { return "汪汪"; } // 重写:签名相同、运行时覆盖
}重写(override)规则:方法名与参数列表必须相同;返回类型协变(子类可返回更具体类型);访问权限不能更严;private/static/final 方法不能被重写。@Override 注解让编译器帮你校验「是不是真的重写」,避免把 sound() 误写成 sond() 变成新方法。

四、多态:向上转型 + 动态绑定
java
Animal a1 = new Dog("旺财"); // 向上转型:父类引用指向子类对象
Animal a2 = new Cat("咪咪");
a1.sound(); // 汪汪 —— 运行时按「实际对象类型」分派
a2.sound(); // 喵喵
static void letItSound(Animal a) { System.out.println(a.sound()); }
letItSound(new Dog("x")); // 同一个方法,传不同子类有不同行为编译看左边,运行看右边:编译器只允许调用 Animal 上声明的方法;真正执行哪个 sound(),由 JVM 在运行时根据堆上的实际对象决定(动态绑定/虚方法分派)。要调用子类独有方法需向下转型:if (a instanceof Dog d) d.watchHome();(Java 16 模式变量)。
坑
- 构造器里调用可被重写的方法:父类构造期间子类字段还未初始化,多态会调到子类重写版并读到默认值,引发隐蔽 NPE——构造器只调 private/final/静态方法。
- 重载(overload)当重写(override):重载看参数列表、是编译期静态分派;重写看继承、是运行期动态分派,两者别混。
- 滥用继承:继承破坏封装(子类依赖父类实现细节),父类改动可能波及子类;只是为了复用优先用组合(持有对象引用),is-a 关系才用继承。
- equals 重写不当:多态集合里自定义对象若不重写
equals/hashCode,contains 等按身份比较;重写要保证相同契约。 - 忘记 super(...):父类没有无参构造器时,子类构造器首行必须显式
super(参数),否则编译错误。
总结
封装用 private 字段加受控方法守住对象状态,继承用 extends 实现 is-a 复用且 Java 只能单继承、子类构造器先走 super,多态则靠「父类引用指向子类对象 + @Override 方法重写」形成编译看左边、运行按实际对象动态分派的同一调用多形态;实战中构造器别调可重写方法、为复用优先组合而非继承。 抽象契约如何在接口与抽象类间取舍见接口与抽象类。