1. 什麼是初始化區塊
在 Java 中有兩種初始化區塊:
- 實例(非靜態)初始化區塊 — 在每次建立新物件時執行,發生在欄位初始化之後、建構子呼叫之前。
- 靜態初始化區塊 — 在類別載入至記憶體時執行一次(在建立第一個物件或存取靜態欄位/方法之前)。
實例初始化區塊
在類別本體中直接宣告,不需要像 static 之類的關鍵字:
public class User {
private String name;
// 實例初始化區塊
{
System.out.println("正在執行實例初始化區塊!");
name = "預設名稱";
}
public User() {
System.out.println("正在執行建構子!");
}
}
靜態初始化區塊
使用關鍵字 static 宣告:
public class Config {
public static String appName;
static {
System.out.println("正在執行靜態初始化區塊!");
appName = "我的超級應用程式";
}
}
2. 初始化順序:誰先誰後?
在 Java 中,類別元素的初始化順序不是隨便亂來,而是有嚴格定義的序列。若你曾試過不看說明書就組裝 IKEA 家具,就能理解為什麼順序很重要:步驟一旦弄錯,衣櫃就可能變成藝術品。
初始化的順序:
- 靜態欄位與靜態區塊 — 依宣告順序,類別載入時執行一次。
- 實例欄位與實例區塊 — 依宣告順序,每次建立新物件時執行。
- 建構子 — 在所有實例初始化之後執行。
示意圖
+-------------------------------+
| 將類別載入 JVM |
+-------------------------------+
| 1. 靜態欄位 |
| 2. 靜態區塊 |
| ↓ |
| 建立物件 |
| ↓ |
| 3. 實例欄位 |
| 4. 實例區塊 |
| 5. 建構子 |
+-------------------------------+
範例:輸出順序
我們寫個類別來示範實際的執行順序:
public class Demo {
static String staticField = print("1. static 欄位");
static {
print("2. static 區塊");
}
String field = print("3. 實例欄位");
{
print("4. 實例區塊");
}
public Demo() {
print("5. 建構子");
}
static String print(String msg) {
System.out.println(msg);
return msg;
}
public static void main(String[] args) {
System.out.println("建立第一個 Demo 物件:");
Demo d1 = new Demo();
System.out.println("\n建立第二個 Demo 物件:");
Demo d2 = new Demo();
}
}
畫面上會看到什麼?
1. static 欄位
2. static 區塊
建立第一個 Demo 物件:
3. 實例欄位
4. 實例區塊
5. 建構子
建立第二個 Demo 物件:
3. 實例欄位
4. 實例區塊
5. 建構子
請注意: 靜態部分(static)只會執行一次 —— 在首次存取類別時。凡是不屬於 static 的,則會在每次建立物件時執行。
3. 程式碼範例:為什麼要用初始化區塊
當建構子不夠用時
有時候,部分初始化必須在所有建構子中都一致執行。比方說類別有多個建構子,而你不想在每個建構子裡重複相同的初始化。這時就很適合把它提到實例初始化區塊:
public class Person {
private String id;
private String name;
{
// 此段程式碼會在任何建構子之前執行
id = java.util.UUID.randomUUID().toString();
System.out.println("產生唯一的 id: " + id);
}
public Person() {
System.out.println("Person() 無參數");
}
public Person(String name) {
this.name = name;
System.out.println("Person(String name)");
}
}
結果: 建立任何 Person 物件時,會先產生 id,接著才會執行對應的建構子。
初始化複雜的靜態資料
靜態區塊常用於初始化「沈重」或複雜的靜態欄位,例如從檔案讀取設定、建立集合、連線到資料庫等。
public class Settings {
public static final java.util.Map<String, String> DEFAULTS;
static {
DEFAULTS = new java.util.HashMap<>();
DEFAULTS.put("theme", "light");
DEFAULTS.put("language", "ru");
System.out.println("Settings 的靜態區塊:預設設定");
}
}
4. 實用細節
何時該使用初始化區塊
適用情境
- 當你需要在所有建構子之前做共用初始化時。
- 當靜態資料很複雜,無法用單純賦值表達時。
- 用來初始化靜態資源(例如應用程式啟動時讀取設定檔)。
不建議使用的情況
- 若可用一般的賦值或建構子解決,就用那些方式。
- 不要把商業邏輯藏在初始化區塊裡 —— 會讓程式難讀、難維護。
- 如果初始化仰賴建構子的參數,請在建構子內進行。
別過度使用初始化區塊
初始化區塊很強大,但並非常用工具。大多數情況下,單純賦值或建構子就足夠。若類別裡有太多初始化區塊,程式會變得難以閱讀與維護。
不要在實例初始化區塊處理依賴建構子參數的邏輯
實例初始化區塊在建構子之前執行,因此無法使用建構子的參數。若需要根據參數進行初始化,請在建構子內處理。
靜態區塊與繼承
靜態初始化區塊不會被繼承。每個類別都有自己的靜態區塊。當載入子類別時,會先執行父類別的靜態區塊,然後才是子類別的靜態區塊。
5. 使用初始化區塊的常見錯誤
錯誤 1:以為實例初始化區塊能取得建構子參數。
許多新手會嘗試在實例初始化區塊中使用建構子參數,但結果不是編譯錯誤,就是得到出乎意料的結果。請記住:實例初始化區塊在建構子之前執行,因此當下還沒有任何參數。
錯誤 2:在初始化區塊裡放入過多邏輯。
若初始化區塊中出現複雜邏輯,程式就會變得混亂。建議把主要工作放在建構子或獨立方法中。
錯誤 3:多個靜態區塊的宣告順序造成問題.
若類別中有多個靜態區塊,它們會依照在程式碼中的宣告順序與靜態欄位一起執行。若其中一個區塊依賴另一個區塊的結果,可能導致出乎意料的行為。
錯誤 4:以為靜態區塊會在每次建立物件時執行。
static 區塊只會在類別載入時執行一次。若你期待重複初始化,這是不會發生的。
錯誤 5:嘗試在靜態區塊中存取實例欄位。
在靜態區塊中只能使用靜態變數與方法。嘗試存取實例(一般)欄位會導致編譯錯誤。
GO TO FULL VERSION