static {} 是類初始化階段的核心執行區,承擔著靜態資源加載、驅動註冊、單例構建等關鍵職責。本文從類加載生命周期切入,系統講解執行時機、順序規則、常見誤區及三大經典應用場景。
<clinit> 方法),僅執行一次;new、反射、反序列化);Class.forName() / ClassLoader.loadClass();MethodHandle / VarHandle 解析靜態字段/方法。static final 編譯期常量(ConstantValue 屬性);StaticTest t = null;);Class.forName("X", false, loader) 參數 false 禁止初始化。<init>。public class OrderTest {
static { System.out.println("1. 父類 static 塊 1"); }
static int a = print("2. 父類靜態字段 a");
static { System.out.println("3. 父類 static 塊 2"); }
static int b = print("4. 父類靜態字段 b");
static int print(String s) { System.out.println(s); return 0; }
public static void main(String[] args) {
System.out.println("5. main 方法");
}
}
// 輸出:
// 1. 父類 static 塊 1
// 2. 父類靜態字段 a
// 3. 父類 static 塊 2
// 4. 父類靜態字段 b
// 5. main 方法
// mysql-connector-java 內部實現
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
static {
try {
DriverManager.registerDriver(new Driver()); // 向 DriverManager 註冊自身
} catch (SQLException e) {
throw new RuntimeException("Can't register driver!");
}
}
}
// 應用端只需 Class.forName("com.mysql.cj.jdbc.Driver") 觸發 static 塊,無需手動註冊
public class HibernateUtil {
private static final SessionFactory SESSION_FACTORY;
static {
try {
Configuration cfg = new Configuration().configure();
StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
.applySettings(cfg.getProperties()).build();
SESSION_FACTORY = cfg.buildSessionFactory(registry);
} catch (Throwable ex) {
throw new ExceptionInInitializerError(ex);
}
}
public static SessionFactory getSessionFactory() { return SESSION_FACTORY; }
public static void shutdown() { getSessionFactory().close(); }
}
優勢:類加載時構建,天然線程安全,避免雙重檢查鎖(DCL)的指令重排風險。
public class NativeLibLoader {
static {
String os = System.getProperty("os.name").toLowerCase();
String libName = os.contains("win") ? "mylib.dll" : "libmylib.so";
// 從 JAR 內釋放到臨時目錄再加載
String path = extractFromJar(libName);
System.load(path); // 必須用絕對路徑
}
// 業務方法直接調用 native 方法
public native int compute(int x, int y);
}
| 誤區 | 真相 | 修正建議 |
|---|---|---|
| static 塊拋受檢異常 | 只能拋非受檢異常,否則編譯錯誤 | 捕獲並包裝為 ExceptionInInitializerError |
| static 塊中使用實例字段/方法 | 編譯報錯 "non-static variable cannot be referenced" | 僅訪問 static 成員,或延遲到實例方法中初始化 |
| 循環依賴導致死鎖 | 類 A static 引用 B,B static 引用 A,觸發 ClassCircularityError | 重構依賴,或改用懶加載(Holder 模式) |
| 以為 static 塊會隨對象創建重複執行 | 類生命周期中僅一次 | 理解「類加載 ≠ 對象創建」 |
Class.forName 參數控制初始化 clazz = Class.forName("com.example.HeavyClass", false, classLoader);
// 後續手動觸發初始化
clazz.getDeclaredField("INSTANCE").get(null); // 訪問靜態字段觸發 <clinit>
此技巧常用於插件系統:掃描類路徑發現插件類,先不初始化,待用戶啟用時再按需觸發。
「static 塊是類的『靜態構造函數』,承載著資源獲取的重擔。搞清觸發條件與順序,才能駕馭單例、SPI、JNI 等高級模式,避免初始化死鎖與資源洩漏。」