| 規則 | 要求 |
| 方法簽名 | 名稱、參數列表完全相同 |
| 返回類型 | 相同或協變(子類型) |
| 訪問權限 | ≥ 父類(public > protected > default > private) |
| 拋出異常 | ≤ 父類(子類異常為父類異常子類型或無) |
| final/private/static | 父類方法有此修飾符 → 不可覆寫 |
@Override 註解:強烈建議
- 編譯期驗證確實覆寫了父類方法(防拼寫錯誤、簽名不匹配)。
- 文檔化意圖,提高可讀性。
構造器鏈:初始化義務
規則
- 子類構造器首行必須調用父類構造器(
super(...) 或隱式 super())。
- 父類無無參構造器 → 子類必須顯式
super(args)。
- 構造器不繼承,但子類可通過
super 復用父類初始化邏輯。
完整初始化順序演示
class Base {
int x = 1; // 1. 父類字段顯式初始化
{ x = 2; } // 2. 父類實例初始化塊
Base() { x = 3; } // 3. 父類構造器
}
class Derived extends Base {
int y = 10; // 4. 子類字段顯式初始化
{ y = 20; } // 5. 子類實例初始化塊
Derived() { y = 30; } // 6. 子類構造器
}
// new Derived() → x=3, y=30
實戰:構造器參數傳遞
class Vehicle {
protected String brand;
public Vehicle(String brand) { this.brand = brand; }
}
class Car extends Vehicle {
private int doors;
// 透傳品牌參數
public Car(String brand, int doors) {
super(brand); // 必須首行
this.doors = doors;
}
}
字段遮蔽:不推薦,易誤用
class Base { int value = 1; }
class Derived extends Base { int value = 2; } // 遮蔽而非覆寫
Base b = new Derived();
System.out.println(b.value); // 1 (父類字段)
System.out.println(((Derived)b).value); // 2 (子類字段)
最佳實踐:字段 private + getter/setter,避免遮蔽。
繼承設計判斷:is-a vs has-a
三種類關係
| 關係 | 關鍵字 | 判斷標準 | 代碼體現 |
| 無關 | — | 無交互 | 獨立類 |
| 使用 (has-a) | 組合/聚合 | A 擁有 B 對象 | class House { Door door; } |
| 繼承 (is-a) | extends | A 是 B 的一種 | class Boss extends Monster |
判斷口訣
「A 是 B 的一種」→ 繼承
「A 有一個 B」→ 組合
「A 使用 B」→ 依賴/關聯
經典反例:正方形繼承長方形
// 違反 LSP
class Rectangle {
protected int width, height;
public void setWidth(int w) { width = w; }
public void setHeight(int h) { height = h; }
}
class Square extends Rectangle {
@Override public void setWidth(int w) { width = height = w; }
@Override public void setHeight(int h) { width = height = h; }
}
// 使用方期望獨立設置寬高
void resize(Rectangle r) {
r.setWidth(10);
r.setHeight(20);
assert r.getWidth() == 10 && r.getHeight() == 20; // Square 失敗!
}
修正:組合(Square 包含 Rectangle)或共同實現 Shape 介面。
設計方法論
自上而下(框架/領域建模)
- 識別核心抽象 → 定義抽象父類/介面。
- 提取共同字段/方法上拉。
- 定義模板方法/鉤子。
- 派生具體子類實現變化點。
自下而上(重構/遺留代碼)
- 編寫具體類滿足當前需求。
- 發現重複代碼/字段。
- 重構提取公共父類(Extract Superclass)。
- 引入介面解耦。
組合優於繼承:設計原則
- 繼承 = 白盒復用:子類依賴父類實現細節,耦合極強,父類變更波及所有子類(脆弱基類問題)。
- 組合 = 黑盒復用:通過介面依賴,內部實現可變,運行期可動態替換組件。
策略模式:用組合替換繼承
// 繼承版:每種移動一個子類,爆炸
class FlyingMonster extends Monster { ... }
class SwimmingMonster extends Monster { ... }
// 組合版:移動策略接口
interface MoveStrategy { void move(Monster m); }
class FlyStrategy implements MoveStrategy { ... }
class SwimStrategy implements MoveStrategy { ... }
class Monster {
private MoveStrategy strategy;
public Monster(MoveStrategy s) { this.strategy = s; }
public void move() { strategy.move(this); }
public void setStrategy(MoveStrategy s) { this.strategy = s; } // 運行期切換
}
// 使用
Monster m = new Monster(new FlyStrategy());
m.move(); // 飛行
m.setStrategy(new SwimStrategy());
m.move(); // 游泳
總結:何時用繼承
- is-a 關係明確(語義上子類是父類特化)。
- 需共享實現代碼(非單純契約復用)。
- 父類穩定(不常變更,或變更可控)。
- 層次淺(≤ 3 層,深層繼承維護困難)。
「優先組合,必要時繼承」—— 設計模式核心智慧。
- 上一篇文章: Java 多態機制深度解析:對象類型多態、方法多態、向上轉型與動態分發
- 下一篇文章: Java 面向對象設計方法:從類抽象到系統構建的核心步驟
Copyright ©
程式碼之家 All Rights Reserved