abstract class 與 interface 是 Java 支援抽象定義的兩大機制,二者相似卻本質不同。選型不當會導致架構僵化、擴展困難。本文從語法層面、設計哲學、演進歷程、實戰決策四個維度深度剖析。
| 特性 | abstract class (JDK 8) | interface (JDK 8+) |
|---|---|---|
| 繼承/實現 | 單繼承 extends | 多實現 implements |
| 構造器 | 可有構造器 (子類隱式調用) | 無構造器 |
| 成員變數 | 任意修飾符、非 final | 隱式 public static final |
| 方法預設實現 | 任意方法可有實現 | default 方法 (JDK 8)、static 方法 (JDK 8)、private 方法 (JDK 9) |
| 訪問修飾符 | 任意 | 隱式 public (JDK 9 允許 private) |
| 適用場景 | "is-a" 強關聯、共享狀態/邏輯 | "can-do" 能力契約、多型行為 |
Serializable、Comparable、Runnable)Door 核心行為:open()、close()。新需求:增加報警功能 alarm()。
// 錯誤:將 Door 本質行為與 Alarm 獨立概念強耦合
interface Door {
void open();
void close();
void alarm(); // 不屬於 Door 核心職責
}
後果:純門禁系統被迫實現 alarm();報警邏輯變更波及所有門類型。
// 核心門契約
interface Door {
void open();
void close();
}
// 獨立報警契約
interface Alarm {
void alarm();
}
// 具體實現:組合而非繼承
class AlarmDoor implements Door, Alarm {
private final AlarmSystem alarmSystem; // 委派實現
public void open() { /* 開門邏輯 */ }
public void close() { /* 關門邏輯 */ }
public void alarm() { alarmSystem.trigger(); }
}
// 普通門無需報警
class SimpleDoor implements Door {
public void open() { /* ... */ }
public void close() { /* ... */ }
}
abstract class BaseDoor implements Door {
// 共享狀態
protected boolean locked = false;
// 模板方法:核心流程固化
public final void open() {
if (locked) throw new IllegalStateException("Door locked");
doOpen();
onOpened();
}
protected abstract void doOpen(); // 子類實現具體開門方式
protected void onOpened() {} // 鉤子方法
}
class SlidingDoor extends BaseDoor {
protected void doOpen() { /* 滑動開門 */ }
}
class SwingDoor extends BaseDoor {
protected void doOpen() { /* 旋轉開門 */ }
}
default 方法:介面提供預設實現,不破壞現有實現類static 方法:工具方法歸屬介面,如 Stream.of()private 方法 (JDK 9):default 方法間共享邏輯ServiceLoader)
// 組合方式實現 AlarmDoor (優於繼承 AlarmDoorBase)
class AlarmDoor implements Door {
private final Door delegate; // 基礎門功能
private final Alarm alarm; // 報警功能
public AlarmDoor(Door delegate, Alarm alarm) {
this.delegate = delegate;
this.alarm = alarm;
}
public void open() { delegate.open(); }
public void close() { delegate.close(); }
public void alarm() { alarm.alarm(); }
}
優勢:運行時動態組合、單一職責、易測試、避免繼承層級爆炸。
抽象類定義「它是什麼」,介面定義「它能做什麼」。
default 提供演進兼容性