static 是 Java 中「屬於類而非對象」的核心修飾符。它重塑了內存佈局、生命周期與訪問方式,是工具類、單例模式、常量定義、靜態代碼塊初始化等經典模式的基石。本文從 JVM 視角深度剖析其語義、內存模型、初始化時序、進階用法與常見誤區。
| 修飾對象 | 稱呼 | 核心語義 | 訪問方式 |
|---|---|---|---|
| 字段 | 靜態變量 / 類變量 | 全局唯一副本,所有實例共享,不依賴實例存在 | 類名.字段名(推薦)或 實例.字段名(不推薦,編譯器警告) |
| 方法 | 靜態方法 / 類方法 | 無 this,不能直接訪問非靜態成員,可被繼承但不可重寫(隱藏) | 類名.方法名() |
| 代碼塊 | 靜態代碼塊 | 類加載階段執行一次,用於類級初始化邏輯 | 自動觸發,無需調用 |
| 內部類 | 靜態內部類 | 不持有外部類引用,獨立存在,延遲加載利器 | 外部類.內部類 |
sizeof(object) 不包含靜態字段。// 觸發類初始化的 5 種情況(JLS 12.4.1):
// 1. new 實例 2. 調用靜態方法 3. 訪問靜態字段(非常量) 4. 反射 Class.forName 5. 初始化子類(先初始化父類)
class StaticDemo {
static { System.out.println("1. 父類靜態塊"); } // ①
static int a = 10; // ②
static { System.out.println("2. 父類靜態塊"); } // ③
{ System.out.println("父類實例塊"); } // 實例初始化時
StaticDemo() { System.out.println("父類構造器"); }
}
class Sub extends StaticDemo {
static { System.out.println("3. 子類靜態塊"); } // ④
static int b = 20; // ⑤
{ System.out.println("子類實例塊"); }
Sub() { System.out.println("子類構造器"); }
}
// 輸出順序(首次 new Sub() 或訪問 Sub.b):
// 1. 父類靜態塊 → 2. 父類靜態塊 → 3. 子類靜態塊 → 4. 父類實例塊 → 5. 父類構造器 → 6. 子類實例塊 → 7. 子類構造器
import static java.lang.Math.PI;
import static java.util.Collections.emptyList;
import static com.utils.StringUtils.*;
public class Test {
void foo() {
double r = PI * 2; // 直接用 PI
List<String> list = emptyList(); // 直接用 emptyList
String s = join(",", "a", "b"); // 直接用 join
}
}
public class Singleton {
private Singleton() {}
private static class Holder {
static final Singleton INSTANCE = new Singleton(); // 類加載時初始化,JVM 保證線程安全
}
public static Singleton getInstance() {
return Holder.INSTANCE; // 首次調用觸發 Holder 加載
}
}
public class Constants {
// 編譯期常量:基本類型/String,編譯器內聯到調用處,修改需重新編譯調用方
public static final int MAX_SIZE = 100;
public static final String APP_NAME = "MyApp";
// 運行期常量:對象引用,不內聯,修改只需重新編譯定義類
public static final List<String> DEFAULT_LIST = Collections.unmodifiableList(Arrays.asList("a", "b"));
public static final Date BUILD_TIME = new Date(); // 注意:對象內部狀態可變!
}
public final class StringUtils { // final 防繼承
private StringUtils() { throw new AssertionError(); } // 防實例化/反射
public static boolean isBlank(String s) { return s == null || s.trim().isEmpty(); }
public static String join(CharSequence delimiter, CharSequence... elements) { /* ... */ }
}
class Parent {
static void foo() { System.out.println("Parent.foo"); }
void bar() { System.out.println("Parent.bar"); }
}
class Child extends Parent {
static void foo() { System.out.println("Child.foo"); } // 隱藏,非重寫
@Override void bar() { System.out.println("Child.bar"); } // 重寫
}
public class Test {
public static void main(String[] args) {
Parent p = new Child();
p.foo(); // 輸出 "Parent.foo" —— 靜態綁定,看引用類型
p.bar(); // 輸出 "Child.bar" —— 動態綁定,看對象類型
Child.foo(); // "Child.foo"
Parent.foo(); // "Parent.foo"
}
}
@Override 會報錯。| 誤區 | 真相 | 正確做法 |
|---|---|---|
| 靜態方法可用 this/super | 無 this,隸屬類而非實例 | 直接用類名訪問靜態成員 |
| 靜態字段屬於對象 | 屬於類,所有實例共享一份 | 理解內存佈局,避免多線程競態 |
| static final 對象不可變 | 引用不可變,對象內部狀態可變 | 用不可變集合/防御性拷貝/不可變對象模式 |
| 靜態代碼塊可拋受檢異常 | 只能拋運行時異常或try-catch包裹 | 異常轉為 ExceptionInInitializerError |
| 繼承靜態方法可重寫 | 只能隱藏,無多態 | 不要指望靜態方法多態,用實例方法 |
static 打破了「一切皆對象」的純粹性,引入了「類級別」的靜態維度。它通過方法區存儲、類加載初始化、無 this 語義、靜態綁定等機制,為全局狀態管理、工具類封裝、單例實現、常量定義提供了語言級支持。深入理解其內存佈局、初始化時序、隱藏vs重寫、線程安全陷阱,是寫出健壯、高性能、可維護 Java 代碼的必修內功。