在 API 設計與框架開發中,有時需要強制禁止類別被繼承,以確保不變條件、安全性或效能。Java 提供兩種主要手段:官方的 final 關鍵字,與非官方的「私有建構子 + 靜態工廠方法」單例模式變體。本文深度解析兩者原理、適用場景、限制與最佳實務。
// 最終類別:不可被繼承
public final class ImmutableConfig {
private final String configValue;
public ImmutableConfig(String configValue) {
this.configValue = configValue;
}
public String getConfigValue() {
return configValue;
}
}
// 編譯錯誤:cannot inherit from final ImmutableConfig
// public class HackConfig extends ImmutableConfig { }
final 確保不可變
public class SingletonGuard {
// 私有建構子:外部無法直接 new,子類無法呼叫 super()
private SingletonGuard() {
// 可加入初始化邏輯
}
// 靜態工廠方法:控制實例建立
public static SingletonGuard getInstance() {
return InstanceHolder.INSTANCE;
}
// 靜態內部類延遲初始化(執行緒安全)
private static class InstanceHolder {
static final SingletonGuard INSTANCE = new SingletonGuard();
}
// 業務方法
public void doSomething() { ... }
}
// 編譯錯誤:SingletonGuard() has private access in SingletonGuard
// public class SubClass extends SingletonGuard { }
super()private → 子類無法存取 → 編譯失敗public 無參建構子,此時可被繼承| 維度 | final 關鍵字 | 私有建構子模式 |
|---|---|---|
| 語意清晰度 | 極高:語言層面明確表達「不可繼承」 | 間接:利用存取權限副作用達到目的 |
| 繼承阻擋強度 | 絕對(編譯期) | 絕對(編譯期) |
| 實例化控制 | 無(可 public 建構子) | 完全控制(單例、多例、建造者等) |
| 介面實作 | 可實作介面 | 可實作介面 |
| 組合優於繼承 | 鼓勵組合 | 強制組合(無法繼承只能持有實例) |
| 序列化相容性 | 需注意 serialVersionUID | 單例需實作 readResolve() 防反序列化破壞 |
| 測試友善度 | 難 Mock(需 PowerMock/模組化) | 同樣難 Mock,但可透過介面依賴注入解決 |
使用組合/裝飾模式而非繼承:
// final 類別
public final class HttpClient {
public Response send(Request req) { ... }
}
// 擴充功能:裝飾器
public class LoggingHttpClient implements HttpClient {
private final HttpClient delegate;
private final Logger logger;
public LoggingHttpClient(HttpClient delegate, Logger logger) {
this.delegate = delegate;
this.logger = logger;
}
@Override
public Response send(Request req) {
logger.info("Request: {}", req);
Response resp = delegate.send(req);
logger.info("Response: {}", resp);
return resp;
}
}
// 僅允許列出的類別繼承
public sealed class Shape permits Circle, Rectangle, Triangle { }
// final 子類:不可再被繼承
public final class Circle extends Shape { ... }
// non-sealed 子類:可被任意繼承
public non-sealed class Rectangle extends Shape { ... }
// sealed 子類:繼續限制繼承鏈
public sealed class Triangle extends Shape permits RightTriangle, EquilateralTriangle { }
final:除非明確設計為可繼承,否則類別宣告加上 final(Effective Java Item 19)。interface,實作類別 final 或 package-private。final 是語意正確、閱讀性最佳的防繼承手段;私有建構子模式則結合實例化控制與防繼承,適合單例、工廠等受控建立場景。Java 17+ 的 sealed 類別提供了介於兩者之間的受控繼承能力。選擇原則:能 final 就 final,需控制建構時用私有建構子,需受限繼承體系用 sealed。