繼承核心概念
- 定義:子類通過
extends 關鍵字繼承父類,獲得其非私有成員 (字段、方法、嵌套類)
- 單繼承:Java 類只能直接繼承一個父類,但可多層繼承形成鏈
- 傳遞性:孫類間接擁有祖父類成員
- 目的:代碼復用、建立「是一種」關係、實現多態
語法與基本示例
父類:Employee (僱員)
public class Employee {
private String name;
private int id;
private double salary;
private String department;
public Employee() {}
public Employee(String name, int id, double salary, String department) {
this.name = name; this.id = id; this.salary = salary; this.department = department;
}
// Getter/Setter...
public void work() { System.out.println(name + " 正在工作..."); }
@Override public String toString() {
return "Employee{name='" + name + "', id=" + id + ", salary=" + salary + ", dept='" + department + "'}";
}
}
子類:Manager (經理) - 擴展特有屬性
public class Manager extends Employee {
private double bonus; // 績效獎金
public Manager() { super(); }
public Manager(String name, int id, double salary, String department, double bonus) {
super(name, id, salary, department); // 必須首行調用父構造器
this.bonus = bonus;
}
public double getBonus() { return bonus; }
public void setBonus(double bonus) { this.bonus = bonus; }
// 方法重寫:擴展工作行為
@Override
public void work() {
super.work(); // 調用父類邏輯
System.out.println("並審核團隊報告,發放獎金 " + bonus);
}
@Override
public String toString() {
return super.toString() + ", bonus=" + bonus;
}
}
測試代碼
public class TestInheritance {
public static void main(String[] args) {
Manager mgr = new Manager("張三", 1001, 15000, "技術部", 5000);
System.out.println(mgr); // 輸出含 bonus
mgr.work(); // 調用重寫方法
// 多態:父類引用指向子類對象
Employee emp = new Manager("李四", 1002, 12000, "市場部", 3000);
emp.work(); // 動態綁定:調用 Manager.work()
// emp.bonus 編譯錯誤:父類引用不可訪問子類特有成員
}
}
訪問控制與繼承
| 修飾符 | 同類 | 同包子類 | 同包非子類 | 異包子類 | 異包非子類 |
private | ✅ | ❌ | ❌ | ❌ | ❌ |
default (包私有) | ✅ | ✅ | ✅ | ❌ | ❌ |
protected | ✅ | ✅ | ✅ | ✅ | ❌ |
public | ✅ | ✅ | ✅ | ✅ | ✅ |
關鍵規則
- 子類**不可直接訪問**父類
private 成員,需通過 public/protected 方法間接訪問
protected 允許**異包子類**訪問,這是為繼承設計的關鍵修飾符
- 構造器**不被繼承**,但子類構造器**必須**調用父類構造器 (
super(...) 或隱式 super())
方法重寫 vs 重載
| 特性 | 重寫 | 重載 |
| 發生位置 | 父子類之間 | 同一類內部 |
| 方法簽名 | 完全相同 (名稱、參數列表) | 名稱相同,參數列表不同 |
| 返回類型 | 兼容 (协变返回类型 Java 5+) | 可不同 |
| 異常拋出 | 子類異常 ⊆ 父類異常 | 無限制 |
| 訪問權限 | 子類 ≥ 父類 (不能縮小) | 無限制 |
| 綁定時機 | 運行時動態綁定 (多態) | 編譯時靜態綁定 |
重寫規則口訣
兩同兩小一大:
方法名相同、參數列表相同
返回類型小於等於、拋出異常小於等於
訪問權限大於等於
錯誤示例
class Parent { public void doWork() {} }
class Child extends Parent {
// ❌ 訪問權限縮小
private void doWork() {}
// ❌ 拋出更大異常
public void doWork() throws Exception {} // 父類無異常
// ❌ 參數列表不同 -> 這是重載,非重寫
public void doWork(int x) {}
}
字段隱藏
class Parent { String name = "Parent"; }
class Child extends Parent { String name = "Child"; }
public class Test {
public static void main(String[] args) {
Child c = new Child();
System.out.println(c.name); // "Child" (子類字段)
System.out.println(((Parent)c).name); // "Parent" (父類字段)
// 字段無多態,靜態綁定到引用類型
}
}
多態實戰模式
1. 模板方法模式
abstract class DataProcessor {
// 最終方法,定義算法骨架
public final void process() {
readData();
doProcess(); // 子類實現
writeData();
}
protected abstract void doProcess();
private void readData() { /* 通用讀取 */ }
private void writeData() { /* 通用寫入 */ }
}
class CsvProcessor extends DataProcessor {
@Override protected void doProcess() { /* CSV 解析 */ }
}
2. 策略模式 (組合優於繼承)
interface PayStrategy { void pay(double amount); }
class Alipay implements PayStrategy { public void pay(double a) { /* ... */ } }
class WechatPay implements PayStrategy { public void pay(double a) { /* ... */ } }
class Order {
private PayStrategy strategy;
public void setPayStrategy(PayStrategy s) { this.strategy = s; }
public void checkout(double amount) { strategy.pay(amount); }
}
Object 類常見重寫
| 方法 | 默認行為 | 重寫目的 | 約定 |
toString() | 類名@哈希碼 | 業務可讀描述 | 建議所有實體重寫 |
equals(Object) | 引用相等 (==) | 業務鍵相等 | 自反、對稱、傳遞、一致、非空 |
hashCode() | 對象地址衍生 | 配合 equals 用於 Hash 集合 | equals 相等 ⇒ hashCode 相等 |
clone() | 淺拷貝 | 深拷貝 | 實現 Cloneable、拋 CloneNotSupportedException |
equals 規範實現 (IDE 生成/Objects.equals)
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee that = (Employee) o;
return id == that.id &&
Double.compare(that.salary, salary) == 0 &&
Objects.equals(name, that.name) &&
Objects.equals(department, that.department);
}
@Override
public int hashCode() {
return Objects.hash(id, name, salary, department);
}
final 關鍵字與繼承限制
final class:不可被繼承 (如 String, Integer)
final method:不可被重寫 (如 Object.getClass())
final field:初始化後不可修改,構造器/聲明處賦值
最佳實踐原則
- 里氏替換原則 (LSP):子類必須能替換父類而不破壞行為
- 組合優於繼承:優先考慮接口 + 組合 (策略、裝飾器),降低耦合
- 設計為繼承或禁止繼承:文檔化可重寫方法、敏感方法加
final
- 構造器不調用可重寫方法:父構造器調用被重寫方法時,子類字段尚未初始化
- @Override 註解:編譯期檢查重寫正確性,防止拼寫錯誤變重載
常見面試題
Q: 構造器能否被繼承?為什麼?
不能。構造器用於初始化本類字段,子類有自己的字段需自己的構造器初始化。子類構造器必須顯式/隱式調用父構造器完成父類部分初始化。
Q: 為什麼 Java 不支持多繼承?接口如何解決?
避免鑽石問題 (字段/方法衝突)。接口默認方法 (Java 8+) 可提供實現,類可實現多個接口實現多重行為復用。
Q: 父類靜態方法能否被重寫?
不能。靜態方法屬於類,不屬於實例,子類定義同名靜態方法稱為**隱藏**,調用由引用類型決定而非對象類型。
Q: super 和 this 的區別?
this 當前對象引用;super 父類對象引用 (調用父構造器、訪問被隱藏父成員)。