Java 面向对象 · 交互课程

封装与继承

封装与继承

目标:理解封装的本质是「数据保护」——private 隐藏数据、public 暴露受控入口;掌握四种访问控制符的可见范围;理解 extends 表达 is-a 关系,掌握成员继承/属性隐藏/覆盖规则与构造方法链。

一、封装:数据保护

面向过程程序的数据对其他模块完全开放,容易被误改且难排查。面向对象的做法:把数据和操作它的代码捆绑进对象内部,对外只暴露受控接口——

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() { /* ... */ }  // 新增方法
}

动手:看继承树

下面这棵树的每个节点是一个类。点击任意类,右侧会显示它的字段/方法、完整继承链,以及蓝色高亮的到根类路径——这就是「成员从哪来」的可视化。

继承关系交互图

点击任意类,查看它的成员、继承链,蓝色高亮的是它到根类的继承路径。

AnimalDogCatWolfPuppy

← 点击左侧的类查看成员

为什么继承:复用带来收益

辨析:继承是「子承父业」(获得能力),不是「继承家产」(花父类的钱)——子对象与父对象是不同的对象,属性值各自独立、互不影响。

三、属性的隐藏(Shadow)

四、覆盖(Override)vs 重载(Overload)

覆盖 Override重载 Overload
发生位置子类 vs 父类同一个类内部
方法签名完全相同(名、参数、返回类型兼容)同名、参数列表不同(类型/顺序/数目)
额外约束访问权限不可窄于父类无
解决什么同名不同实现 → 多态同名不同入参

五、构造方法链

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;
    }
}
尚未执行
  1. 1Dog进入 Dog(String name, String breed)
  2. 2Dog第一个可执行语句 super(name) → 先初始化父类部分
  3. 3Animal进入 Animal(String name)
  4. 4Animalthis.name = name(name="旺财")
  5. 5AnimalAnimal 构造返回,回到 Dog
  6. 6Dogthis.breed = breed(breed="哈士基")
  7. 7DogDog 构造返回,对象创建完成

对象状态(new Dog("旺财", "哈士基"))

执行后这里显示已确定的字段值。

加练:字段隐藏(shadow)——this / super 各命中谁?

class Base { String type; String id; }、class Sub extends Base { String type; }——在 Sub 里访问:

this.type → Sub 的字段super.type → Base 的字段this.id → Base 的字段

要点

思考题

  1. 为什么属性要设为 private?getter/setter 里能做什么「校验」?
  2. Dog is-a Animal、Dog has-a Tail 分别该用哪种复用手段?
  3. 子类隐藏父类同名属性后,如何取回父类的那份?

学习小测验

学习小测验

Q1

把属性设为 private 并提供 public getter/setter 的目的是( )

Q2

default(不写修饰符)与 protected 的关键区别是( )

Q3

子类继承父类后,下面哪个说法正确?

Q4

Java 中一个类最多能 extends 几个父类?

Q5

子类方法「签名与父类完全相同、重新实现」称为?

Q6

Printer is-a Device、Printer has-a InkCartridge 分别表示( )