2187 字
约 7 分钟
6
S04-J08 · 面向对象六讲:封装、继承、多态与四个边角

适用读者:语法已通、但"为什么这么设计"说不清的人;准备一面 OOP 追问的人。
前置:S04-J07、B05(封装继承多态入门)。
覆盖旧文:2722–2738(教程第 21–37 章)+ 2966–2982(同题重复导出)→ 34 篇 / 约 40 万字符。
扩展点:E-S04-024(初始化的真实顺序:静态 → 实例 → 构造)、E-S04-025(重写/重载/隐藏的判定表)、E-S04-026(接口 vs 抽象类的选型判据)。
上一篇:S04-J07 · Java 语法速通:从变量到递归的 24 个坑
下一篇:补编第 3 篇 · 异常体系(待写)。


1. 一句话结论

OOP 的所有细节都归结为两条:成员"什么时候存在"(初始化顺序)和"调用谁"(静态绑定 vs 动态绑定)。 this/super、访问修饰符、重写、多态、内部类,都是这两条的推论。

出处:A-2722–A-2738、A-2966–A-2982(类与对象、构造、继承、多态、接口、内部类)。


2. 初始化的真实顺序(E-S04-024)

class Parent {
    static { System.out.println("1 父静态块"); }
    { System.out.println("3 父实例块"); }
    Parent() { System.out.println("4 父构造"); }
}
class Child extends Parent {
    static { System.out.println("2 子静态块"); }
    { System.out.println("5 子实例块"); }
    Child() { super(); System.out.println("6 子构造"); }
    public static void main(String[] a) { new Child(); }
}

输出:1 → 2 → 3 → 4 → 5 → 6

顺序口诀:父静态 → 子静态 →(父实例块 → 父构造)→(子实例块 → 子构造)。

追问 答
静态块何时执行 类首次主动使用时(new、静态成员访问、反射、子类初始化);只执行一次
实例块有什么用 抽公共初始化逻辑(多个构造器共享),等价于把代码复制进每个构造器
super() 在哪 编译器插在每个构造器的第一行(除非显式写 this(...))
字段初始化在哪一步 在实例块之前执行(源码顺序),字段声明顺序影响结果

坑 1(高频):父构造器里调用被子类重写的方法,会执行子类版本,此时子类字段还是默认值。

class P { P() { print(); } void print() { System.out.println("P"); } }
class C extends P {
    int v = 42;
    void print() { System.out.println("C v=" + v); }   // 输出 "C v=0"
}

结论:构造器里不要调用可被重写的方法(Effective Java 第 19 条)。


3. this / super / 访问修饰符

关键字 用途 边界
this 当前对象引用;this(...) 调本类其他构造器(必须第一行) 静态方法里不能用
super 父类引用;super(...)、super.method() 只能一层,不能跨代
private 类内可见 内部类可访问外部私有成员(编译器生成访问器)
默认(包) 同包可见 —
protected 同包 + 子类(子类跨包只能访问自身继承来的成员) 常见误解点
public 全部可见 —

坑 2:protected 跨包时,子类不能通过"父类引用"访问——只能通过 this 或子类引用:

// 包 a: public class A { protected void m(){} }
// 包 b: class B extends A { void f(A a){ a.m(); } }  // ❌ 编译失败
class B extends A { void f(){ this.m(); } }            // ✅

4. 重写 / 重载 / 隐藏:判定表(E-S04-025)

现象 条件 绑定时机 例子
重载 Overload 同名、参数列表不同(可同类或父子) 编译期(静态) f(int) / f(String)
重写 Override 子类同名同参数、返回值协变、访问权限不更严 运行期(动态) toString()
隐藏 Hiding 静态方法同名同参 编译期(按引用类型) static void f()
字段 永远按引用类型编译期解析 编译期 父子同名 v
class P { String v = "p"; void m() { System.out.println("p"); } }
class C extends P {
    String v = "c";
    void m() { System.out.println("c"); }
}
P x = new C();
System.out.println(x.v);   // p —— 字段不参与多态
x.m();                     // c —— 方法参与多态

判据:只有"实例方法"参与多态;字段、静态方法、私有方法都是编译期解析。

重写的三条硬规则:① 访问权限不能变小;② 不能抛更宽的受检异常;③ final/static/private 方法不能被重写。


5. 多态:靠什么实现

编译期:方法调用写成 invokevirtual + 方法符号引用
运行期:vtable(虚方法表)按对象实际类型查表 → 实际方法入口
追问 答
为什么需要多态 面向接口编程:调用方依赖抽象,实现可替换
性能代价 一次虚表查表 + 无法内联;热点代码 JIT 会做类型特化(去虚化)
instanceof 与 getClass() 前者含子类,后者精确类型
final 的作用 类不可继承 / 方法不可重写 / 变量不可改;用于设计意图与内联优化

坑 3:instanceof 对 null 返回 false(不抛异常);强转 (T) null 合法,但调用方法会 NullPointerException。


6. 抽象类 vs 接口(E-S04-026)

维度 抽象类 接口
实例字段 ✅ 可以有状态 ❌ 只有 static final 常量
构造器 ✅ ❌
方法实现 ✅ 任意 Java 8+ default/static;Java 9+ private
多继承 ❌ 单继承 ✅ 多实现
语义 "是什么"(is-a,共享实现) "能做什么"(can-do,契约)

选型判据:

① 需要保存状态或构造逻辑 → 抽象类
② 需要多方实现、跨继承树复用能力 → 接口
③ 要"演进而不破坏实现者" → 接口 + default 方法(Java 8+)
④ 需要模板方法控制流程 → 抽象类(模板方法模式)

default 方法的菱形冲突:两个接口都有同名 default → 实现类必须重写,或用 Interface.super.m() 指定。


7. 内部类四种与它们的内存含义

类型 写法 是否持有外部实例 关键点
成员内部类 class Inner ✅ 需外部实例才能 new
静态嵌套类 static class Nested ❌ 优先选它,无隐式引用
局部内部类 方法内 class L ✅ 可访问 effectively final 局部变量
匿名内部类 new Runnable(){...} ✅ Lambda 出现后多用于需要状态的场景

坑 4(内存泄漏):非静态内部类持有外部引用,把它交给长生命周期容器(线程池、缓存)会导致外部对象无法回收。静态嵌套类没有这个问题。


8. 反例:五个"看起来对"的错答

说法 问题
"构造器会先初始化子类字段再调父构造" 顺序相反,且父构造里调重写方法会看到默认值
"字段也能多态" 字段按引用类型编译期解析
"protected 就是都能访问" 跨包时受"必须通过子类自身引用"限制
"重载也是运行时决定" 重载是编译期按静态类型选
"内部类和静态嵌套类差不多" 前者隐式持有外部引用,易泄漏

9. 自测

  1. 静态块、实例块、构造器的执行顺序?父与子如何交错?
  2. 为什么构造器里不要调用可重写方法?
  3. 字段与方法在"多态"上的差别?
  4. 抽象类与接口的选型四条判据?
  5. 非静态内部类为什么容易造成内存泄漏?
答案
  1. 父静态→子静态→父实例块→父构造→子实例块→子构造。
  2. 子类字段尚未初始化,重写方法读到默认值。
  3. 方法动态绑定(invokevirtual),字段编译期按引用类型。
  4. 需状态/构造→抽象类;多实现/跨树→接口;平滑演进→default;模板方法→抽象类。
  5. 隐式持有外部实例引用,被长生命周期对象持有时外部无法回收。

10. 下一篇

S04-J09 · 异常体系 —— 受检与非受检的边界、finally 何时不执行、try-with-resources 的抑制异常、以及"吞异常"的三种代价。

S04-J08 · 面向对象六讲:封装、继承、多态与四个边角
https://www.clxhxhhr.top/posts/5231/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。