Java 類中三種代碼塊——靜態代碼塊、構造代碼塊(實例初始化塊)、普通代碼塊——在類加載、對象創建、方法執行不同階段發揮作用。本文通過字節碼視角與實例代碼,徹底釐清執行順序與最佳實踐。
優先級:靜態代碼塊 > main方法 > 構造代碼塊 > 構造方法 其中:靜態代碼塊僅執行一次(類加載時);構造代碼塊每次創建對象執行;普通代碼塊隨方法調用執行。
public class StaticBlockDemo {
static {
System.out.println("1. 靜態代碼塊 A");
}
static {
System.out.println("2. 靜態代碼塊 B");
}
public StaticBlockDemo() {
System.out.println("4. 構造方法");
}
{
System.out.println("3. 構造代碼塊");
}
public static void main(String[] args) {
System.out.println("0. main 方法開始");
new StaticBlockDemo();
System.out.println("--- 分割線 ---");
new StaticBlockDemo();
}
}
/* 輸出:
0. main 方法開始
1. 靜態代碼塊 A
2. 靜態代碼塊 B
3. 構造代碼塊
4. 構造方法
--- 分割線 ---
3. 構造代碼塊
4. 構造方法
*/
static { } 語法,類加載階段(ClassLoader.loadClass)執行,僅一次。this),只能訪問靜態成員。ExceptionInInitializerError,類初始化失敗,後續無法使用。public class InstanceBlockDemo {
{
System.out.println("構造代碼塊 1");
}
{
System.out.println("構造代碼塊 2");
}
public InstanceBlockDemo() {
System.out.println("構造方法");
}
public InstanceBlockDemo(String name) {
System.out.println("帶參構造方法: " + name);
}
public static void main(String[] args) {
new InstanceBlockDemo();
System.out.println("---");
new InstanceBlockDemo("test");
}
}
/* 輸出:
構造代碼塊 1
構造代碼塊 2
構造方法
---
構造代碼塊 1
構造代碼塊 2
帶參構造方法: test
*/
{ } 直接在類體中聲明,無 static 關鍵字。new 創建對象時執行,優先於構造方法體。super() 之後,構造方法體之前)。public class OrdinaryBlockDemo {
public void method() {
System.out.println("方法開始");
{
int x = 10;
System.out.println("普通代碼塊 x=" + x);
}
// x 作用域結束,不可訪問
{
int y = 20;
System.out.println("普通代碼塊 y=" + y);
}
System.out.println("方法結束");
}
public static void main(String[] args) {
new OrdinaryBlockDemo().method();
}
}
/* 輸出:
方法開始
普通代碼塊 x=10
普通代碼塊 y=20
方法結束
*/
{ }。class Parent {
static { System.out.println("P-靜態塊"); }
{ System.out.println("P-構造塊"); }
Parent() { System.out.println("P-構造方法"); }
}
class Child extends Parent {
static { System.out.println("C-靜態塊"); }
{ System.out.println("C-構造塊"); }
Child() { System.out.println("C-構造方法"); }
}
public class InheritanceOrder {
public static void main(String[] args) {
System.out.println("--- main 開始 ---");
new Child();
System.out.println("--- 創建第二個 ---");
new Child();
}
}
/* 輸出:
--- main 開始 ---
P-靜態塊
C-靜態塊
P-構造塊
P-構造方法
C-構造塊
C-構造方法
--- 創建第二個 ---
P-構造塊
P-構造方法
C-構造塊
C-構造方法
*/
父類靜態 → 子類靜態 → 父類構造塊 → 父類構造方法 → 子類構造塊 → 子類構造方法
| 場景 | 推薦做法 | 避坑 |
|---|---|---|
| 單例饿漢模式 | 靜態塊初始化 instance | 異常導致類不可用,捕獲異常或用靜態內部類 |
| 多構造器共同初始化 | 構造塊提取公共代碼 | 注意構造塊執行於 super() 之後,不可依賴父類構造方法初始化的字段 |
| 靜態資源加載(驅動、配置) | 靜態塊 + try-catch 記錄日志 | 避免耗時操作阻塞類加載,考慮延遲初始化 |
| 作用域隔離 | 方法內普通塊限制變量生命周期 | 別濫用,現代 IDE 重構提取方法更清晰 |
| 字段初始化順序陷阱 | 字段聲明處賦值 = 構造塊優先級 | 構造器中調用可覆寫方法導致子類字段未初始化(見 Effective Java 條目 19) |
javap -c ClassName 可見 <clinit>(類初始化器,含靜態塊/靜態字段賦值)和 <init>(實例初始化器,含構造塊/實例字段賦值/構造方法體)。<clinit> 由 JVM 在類首次主動使用時調用,線程安全(JVM 內部鎖)。<init> 每次 new / 反射 / 反序列化 調用。精通三種代碼塊的執行時機、作用域與字節碼映射,能精確控制初始化邏輯,避免繼承鏈下的順序陷阱,編寫更健壯的 Java 類。