「抽象類 vs 接口」是 Java 面試高頻題,也是架構設計的核心抉擇。從 JDK 1.0 到 JDK 17+,兩者邊界不斷模糊(default 方法、static 方法、private 方法、密封類)。本文從語法規則、設計哲學、演進歷史、經典門/警報案例、現代選擇決策矩陣五個維度,構建完整認知體系。
| 特性 | 抽象類 | 接口 |
|---|---|---|
| 繼承關鍵字 | extends(單繼承) | implements(多實現) |
| 構造器 | 可有(子類調用 super()) | 無 |
| 字段類型 | 任意(實例/靜態、final/非 final、任意修飾符) | 隱式 public static final(常量) |
| 方法類型 | 抽象/具體/靜態/私有/最終/同步/本地 | JDK7-: 抽象 JDK8+: default/static/private JDK9+: private static |
| 訪問修飾符 | public/protected/private/package | 隱式 public(JDK9+ 允許 private) |
| 初始化塊 | 實例初始化塊、靜態初始化塊 | 無 |
| 主方法 | 可有 main | JDK8+ 可有 static main |
FighterJet extends Airplane、Sparrow extends Bird。Airplane implements Flyable、Bird implements Flyable、Superman implements Flyable。門有 open()、close();現需增加 alarm() 功能。
abstract class Door {
abstract void open();
abstract void close();
abstract void alarm(); // 強制所有門都有警報,不合理
}
interface Door {
void open();
void close();
void alarm(); // 火災報警器實現此接口,被迫實現 open/close,不合理
}
// 1. 核心能力接口:門的固有行為
interface Door {
void open();
void close();
}
// 2. 擴展能力接口:警報行為
interface Alarm {
void alarm();
}
// 3. 抽象類:門的共性模板(可選,若有共享邏輯/狀態)
abstract class BaseDoor implements Door {
private String brand;
// 共享字段、通用邏輯...
public void open() { /* 通用開門邏輯 */ }
public void close() { /* 通用關門邏輯 */ }
}
// 4. 具體實現:報警門 = 門 + 警報
class AlarmDoor extends BaseDoor implements Alarm {
@Override
public void alarm() { /* 報警邏輯 */ }
}
// 5. 純警報器:僅有警報能力
class FireAlarm implements Alarm {
@Override
public void alarm() { /* 火災報警 */ }
}
核心原則:接口隔離原則 (ISP)——客戶端不應依賴它不需要的方法。將「門的固有行為」與「延伸的警報行為」拆分為獨立接口,再按需組合。
| 版本 | 接口新增能力 | 影響 |
|---|---|---|
| JDK 7 | 無(僅抽象方法、常量) | 邊界清晰:抽象類有實現、接口無實現 |
| JDK 8 | default 方法、static 方法 | 接口可提供默認實現,減少抽象類必要性 |
| JDK 9 | private 方法、private static 方法 | 接口內部封裝邏輯,進一步逼近抽象類 |
| JDK 15+ | sealed 密封接口/類 | 可控制實現/繼承範圍,雙方均支持 |
現狀:接口已具備「默認實現、靜態工具方法、私有輔助方法」,唯一缺失的是實例字段(狀態)和構造器。抽象類保留「狀態管理、構造器鏈、非 public 封裝」優勢。
| 場景 | 推薦選擇 | 理由 |
|---|---|---|
| 需要共享狀態(字段)或構造器邏輯 | 抽象類 | 接口無實例字段、無構造器 |
| 定義類型層次結構(IS-A),核心領域模型 | 抽象類 | 語義清晰、單繼承強約束 |
| 定義橫切能力/契約(CAN-DO),多類複用 | 接口 | 多實現、解耦、可作為 SPI 擴展點 |
| 需要提供默認實現且無狀態依賴 | 接口 default 方法 |
避免抽象類、減少樣板代碼 |
| API 公開給三方擴展,極度重視二進制兼容 | 接口(謹慎加方法) | 抽象類修改易破壞子類二進制兼容 |
| 需要非 public 封裝(protected/private 輔助方法) | 抽象類 | 接口方法隱式 public(JDK9+ 私有方法僅內部可見) |
| 同時需要「共享狀態」和「多實現契約」 | 抽象類 + 接口組合 | 如 AbstractList implements List、AlarmDoor extends BaseDoor implements Alarm |
抽象類與接口的本質區別在於「建模什麼」:抽象類建模「實體類型的共性骨架」(模板),接口建模「行為能力的契約規範」(契約)。JDK 8+ 讓接口擁有默認實現,但狀態與構造器仍是抽象類的專屬護城河。實戰中,優先用接口定義契約,必要時引入抽象類管理共享狀態/邏輯,兩者組合(如集合框架 AbstractList → List)構建最靈活、穩健的類型體系。