protected 與 static 是 Java 中最基礎也最容易被誤解的兩個修飾符。前者關係到封裝與繼承的邊界,後者決定了成員屬於「類」還是「實例」。本文通過標準規範、記憶體模型與實戰陷阱三個維度,徹底講透它們。
protected 成員對以下兩類代碼可見:
// pkg.a
public class Parent {
protected void doSomething() {}
}
// pkg.b
public class Child extends Parent {
public void test(Parent p, Child c) {
c.doSomething(); // ✅ 編譯通過:c 是 Child 實例
p.doSomething(); // ❌ 編譯錯誤:p 是 Parent 實例,雖然 Child 繼承了 Parent,
// 但 Parent 的 protected 成員對 Child 代碼不可見,
// 除非該實例確實是 Child 或其子類型。
}
}
核心原則:子類繼承的是父類的成員,而非父類實例的訪問權。JLS 原文:「protected 成員僅在負責實現該成員的類的子類實例中可存取。」
Object.clone() 被設計為 protected,強制子類覆寫並決定是否對外暴露(通常改為 public 並實現 Cloneable)。這防止了外部代碼直接對任意對象進行淺拷貝,符合封裝原則。
private + protected setter/getter。<clinit>)按聲明順序執行,僅執行一次,天然線程安全(由類鎖保證)。public class Counter {
static int staticCount = 0; // 類變量
int instanceCount = 0; // 實例變量
public void inc() { staticCount++; instanceCount++; }
}
Counter a = new Counter(); a.inc(); // staticCount=1, a.instanceCount=1
Counter b = new Counter(); b.inc(); // staticCount=2, b.instanceCount=1
// a.instanceCount 仍為 1,staticCount 變為 2(共享)
import static pkg.Class.METHOD; 簡化常量/工具方法調用。public class Singleton {
private Singleton() {}
private static class Holder { static final Singleton INSTANCE = new Singleton(); }
public static Singleton getInstance() { return Holder.INSTANCE; }
}
| 誤區 | 正確認知 |
|---|---|
| 靜態方法可用 this/super | 不可,無實例上下文 |
| 子類覆寫父類靜態方法是多態 | 是隱藏,非重寫;調用由編譯時類型決定 |
| 靜態變量序列化 | 不參與預設序列化(transient 效果) |
| 介面中聲明 static 方法需實現 | Java 8+ 介面可有 static 默認實現,不被繼承,只能 Interface.staticMethod() 調用 |
static 變量屬共享資源,多執行緒環境下必須考慮原子性:
AtomicInteger 等原子類。synchronized 或 Lock 保護。static final String)天然安全。| 維度 | protected | static |
|---|---|---|
| 核心語義 | 包內 + 子類(實例級)可見 | 屬於類,非實例 |
| 繼承關係 | 可被子類覆寫(多態) | 可被子類隱藏(非多態) |
| 訪問限制 | 跨包需通過子類實例 | 無實例也可訪問 |
| 典型場景 | 框架模板方法、內部協助 API | 工具類、常量、單例、狀態共享 |
| 線程安全 | 無關 | 共享變量需額外同步 |
理解 protected 的「實例級可見性」與 static 的「類級所有權」,是寫出高內聚、低耦合、線程安全 Java 代碼的基石。實戰中:能私有就私有,能實例就別靜態,繼承體系中謹慎使用 protected 公開內部協議。