封装与继承
封装与继承
目标:理解封装的本质是「数据保护」——
private隐藏数据、public暴露受控入口;掌握四种访问控制符的可见范围;理解extends表达 is-a 关系,掌握成员继承/属性隐藏/覆盖规则与构造方法链。
一、封装:数据保护
面向过程程序的数据对其他模块完全开放,容易被误改且难排查。面向对象的做法:把数据和操作它的代码捆绑进对象内部,对外只暴露受控接口——
- 属性用
private隐藏(如secret); - 提供
public方法作为唯一入口(如info()、getter/setter),在方法里做校验后再改数据。
class SafeBox {
private String secret; // 对外隐藏
void setSecret(String s) { // 受控入口:先校验再修改
if (s != null && s.length() >= 6) this.secret = s;
}
String info() { return "内容:" + secret; }
}
四种访问控制符的可见范围:
交互实验:封装可见性矩阵(EncapsulationMatrix)
点任意格子,查看「该修饰符 × 该上下文」下成员是否可访问及原因。
| 类内部 | 同包其他类 | 不同包子类 | 外部(异包非子类) | |
|---|---|---|---|---|
| public | ||||
| protected | ||||
| 默认(不写修饰符) | ||||
| private |
点格查看说明。
class Dog {
public String name; // 所有上下文可见
protected int age; // 类内 + 子类(经子类引用)
String breed; // 默认:仅包内
private int secret; // 仅类内部
public void info() {
System.out.println(name + ", " + age);
// secret 在这里可访问(类内部)
}
}默认修饰符(不写)=「包级私有」:同包可见,异包子类继承不到;
protected恰好相反——同包其他类不可见,但子类可以。这是最常考的辨析点。
二、继承:extends 表达 is-a
extends 表达「is-a」关系:Dog is-a Animal。子类自动拥有父类的可访问成员,并可新增自己的成员、重写方法。
class Dog extends Animal {
String breed; // 新增字段
void bark() { /* ... */ } // 新增方法
}
动手:看继承树
下面这棵树的每个节点是一个类。点击任意类,右侧会显示它的字段/方法、完整继承链,以及蓝色高亮的到根类路径——这就是「成员从哪来」的可视化。
继承关系交互图
点击任意类,查看它的成员、继承链,蓝色高亮的是它到根类的继承路径。
← 点击左侧的类查看成员
为什么继承:复用带来收益
- is-a 用继承:
Dog is-a Animal——子类「获得」父类的能力并扩展(extends更贴切的译法是「扩展」,inherit兼有「遗传」之意); - has-a 用组合:音乐盒 = 机芯 + 动力源 + 减速机构,各组件功能单一、封装度高、通过预定义接口配合。
辨析:继承是「子承父业」(获得能力),不是「继承家产」(花父类的钱)——子对象与父对象是不同的对象,属性值各自独立、互不影响。
三、属性的隐藏(Shadow)
- 子类继承父类非
private的属性; - 子类定义与父类同名属性时,子类的属性隐藏了父类的——经子类对象访问到的是子类自己的那份;可用
super.属性取回被隐藏的父类属性。
四、覆盖(Override)vs 重载(Overload)
| 覆盖 Override | 重载 Overload | |
|---|---|---|
| 发生位置 | 子类 vs 父类 | 同一个类内部 |
| 方法签名 | 完全相同(名、参数、返回类型兼容) | 同名、参数列表不同(类型/顺序/数目) |
| 额外约束 | 访问权限不可窄于父类 | 无 |
| 解决什么 | 同名不同实现 → 多态 | 同名不同入参 |
五、构造方法链
- 子类没有自己的构造方法时,继承父类的无参构造方法;
- 子类定义了构造方法时:创建对象先执行父类(无参)构造、再执行自己的;
- 要调父类含参构造:
super(参数)必须是子类构造方法的第一个可执行语句。
class Animal {
String name;
Animal() { this("无名"); }
Animal(String name) { this.name = name; }
}
class Dog extends Animal {
String breed;
Dog(String name, String breed) {
super(name); // 必须首句:先让父类部分初始化
this.breed = breed;
}
}
交互实验:构造方法链(ConstructorChain)
点「执行 new」后逐帧看构造顺序:子类构造入口 → super(参数) 首句 → 父类构造体 → 返回 → 子类字段赋值。 右侧对象状态随帧更新——先有父类字段,后有子类字段。
class Animal {
String name;
Animal() { this("无名"); }
Animal(String name) { this.name = name; }
}
class Dog extends Animal {
String breed;
Dog(String name, String breed) {
super(name); // 必须首句:先让父类部分初始化
this.breed = breed;
}
}- 1Dog进入 Dog(String name, String breed)
- 2Dog第一个可执行语句 super(name) → 先初始化父类部分
- 3Animal进入 Animal(String name)
- 4Animalthis.name = name(name="旺财")
- 5AnimalAnimal 构造返回,回到 Dog
- 6Dogthis.breed = breed(breed="哈士基")
- 7DogDog 构造返回,对象创建完成
对象状态(new Dog("旺财", "哈士基"))
执行后这里显示已确定的字段值。
加练:字段隐藏(shadow)——this / super 各命中谁?
class Base { String type; String id; }、class Sub extends Base { String type; }——在 Sub 里访问:
要点
- 单继承:一个类只能
extends一个直接父类。 - 构造器不继承:子类必须自己定义构造器,用
super(...)调父类构造器。 - 方法重写(override):子类可重写父类方法——上转型后的动态绑定见下节《接口与多态》。
思考题
- 为什么属性要设为
private?getter/setter 里能做什么「校验」? Dog is-a Animal、Dog has-a Tail分别该用哪种复用手段?- 子类隐藏父类同名属性后,如何取回父类的那份?
学习小测验
学习小测验
把属性设为 private 并提供 public getter/setter 的目的是( )
default(不写修饰符)与 protected 的关键区别是( )
子类继承父类后,下面哪个说法正确?
Java 中一个类最多能 extends 几个父类?
子类方法「签名与父类完全相同、重新实现」称为?
Printer is-a Device、Printer has-a InkCartridge 分别表示( )