CodeGym /課程 /JAVA 25 SELF /靜態與 final 欄位的初始化

靜態與 final 欄位的初始化

JAVA 25 SELF
等級 15 , 課堂 3
開放

1. 靜態欄位:如何初始化?

靜態欄位(static)不屬於某個物件,而是屬於整個類別。其值在該類別的所有物件之間只有一份。打個比方——就像為所有員工宣告一個「公共基金」:不管誰來使用,大家共用同一個基金。

宣告時初始化

最簡單且最常見的方式,是在宣告欄位時直接賦值:

public class User {
    private static int userCount = 0; // 就在這裡初始化

    // ... 其他程式碼
}

此欄位會在第一次將類別 User 載入記憶體時完成初始化。

在靜態初始區塊中初始化

有時需要更複雜的初始化邏輯——例如從檔案載入資料或進行計算。這種情況可使用靜態初始區塊

public class Config {
    public static String configPath;

    static {
        // 此區塊在類別載入時只會執行一次
        configPath = System.getenv("APP_CONFIG_PATH");
        if (configPath == null) {
            configPath = "/etc/app/default.conf";
        }
        System.out.println("Config path 已初始化: " + configPath);
    }
}

何時會呼叫靜態初始區塊?

  • 首次存取該類別時(例如建立第一個物件,或呼叫任何靜態方法/欄位)。
  • 對每個類別僅執行一次。

存取靜態欄位的要點

  • 應透過類別名稱來存取靜態欄位:User.userCount
  • 也可以透過物件來存取,但這被視為不佳風格(會讓閱讀程式碼的人困惑)。

範例:

User u1 = new User();
User u2 = new User();
System.out.println(User.userCount); // 正確
System.out.println(u1.userCount);   // 能運作,但不建議!

2. final 欄位:何時以及如何初始化?

final 是一個修飾詞,表示「此欄位只能被賦值一次,之後不可再變更」。初始化後,欄位的值即成為不可變:對物件而言是其固定屬性;若欄位是靜態的(static),對類別而言就是共用常數。

使用範例:

  • 常數(例如 PI)。
  • 物件的唯一識別碼,建立後不可變更。

初始化 final 欄位的要求

在 Java 中有嚴格規定:每個 final 欄位必須在宣告時,或在類別的每個建構子中完成初始化

宣告時初始化

public class Circle {
    public static final double PI = 3.1415926535; // 類別常數
    private final String id = "CIRCLE";           // 物件常數
}

在建構子中初始化

有時 final 欄位的值只有在建立物件時才會得知:

public class User {
    private final int id;

    public User(int id) {
        this.id = id; // 在建構子中賦值給 final 欄位
    }
}

重要:如果類別有多個建構子,則必須在每一個建構子中都初始化該 final 欄位!

綜合範例

public class Token {
    private final String value;
    private final long timestamp;

    public Token(String value) {
        this.value = value;
        this.timestamp = System.currentTimeMillis();
    }
}

初始化不當時的編譯錯誤

如果你忘了初始化 final 欄位,編譯器將不允許建置:

public class Broken {
    private final int x; // 未初始化

    public Broken() {
        // 未對 x 賦值!
    }
}
// 錯誤: variable x might not have been initialized

3. 同時使用 static 與 final:宣告類別常數

最常見的組合是 public static final。在 Java 中,這樣用來宣告類別常數——值只指定一次,且在程式執行期間不會變更。這些常數屬於整個類別,且對所有物件都相同。

語法與範例

public class MathUtils {
    public static final double PI = 3.1415926535;
    public static final String APP_NAME = "MyApp";
}

說明:

  • public — 到處都可存取。
  • static — 屬於整個類別,而非某個物件。
  • final — 初始化後不可再變更。

用法

double area = MathUtils.PI * r * r;
System.out.println(MathUtils.APP_NAME);

慣例:常數名稱通常使用全大寫並以底線分隔(UPPER_CASE_WITH_UNDERSCORES)。

4. 程式碼範例:初始化靜態與 final 欄位的各種方式

範例 1:含有常數的簡單類別

public class Constants {
    public static final int DAYS_IN_WEEK = 7;
    public static final String COMPANY = "Romashka LLC";
}

範例 2:在靜態區塊中初始化的靜態欄位

public class AppConfig {
    public static final String DEFAULT_PATH;

    static {
        // 可以執行較複雜的邏輯
        String env = System.getenv("APP_PATH");
        if (env != null) {
            DEFAULT_PATH = env;
        } else {
            DEFAULT_PATH = "/usr/local/app";
        }
    }
}

範例 3:物件的 final 欄位,在建構子中初始化

public class User {
    private static int nextId = 1;
    private final int id;
    private String name;

    public User(String name) {
        this.id = nextId++;
        this.name = name;
    }

    public int getId() { 
        return id; 
    }
    public String getName() {
        return name; 
    }
    public void setName(String name) { 
        this.name = name; 
    }
}

用法:

User u1 = new User("Ivan");
User u2 = new User("Maria");
System.out.println(u1.getId()); // 1
System.out.println(u2.getId()); // 2

範例 4:final 欄位初始化不當所導致的錯誤

public class BadExample {
    private final int number;

    public BadExample() {
        // number 未初始化!
    }
}
// 編譯錯誤: variable number might not have been initialized

5. 操作靜態與 final 欄位的最佳實踐

僅將 public static final 用於真正的常數

如果某個值將來可能會改變(例如員工清單),就不要把它設成 final!常數應該是真正不可變的。

public static final int MAX_USERS = 1000; // 好
public static final String[] USERS = new String[100]; // 不好!

雖然參考本身 USERS 不會改變,但陣列的內容可以被修改。這可能導致意料之外的錯誤。

需要複雜初始化時請使用靜態區塊

如果無法用簡單賦值來表達常數(例如需要計算或從檔案讀取),請使用靜態初始區塊。

別濫用靜態欄位

靜態欄位實質上是全域變數。過多的靜態欄位可能導致難以追蹤的錯誤與維護上的困難。

不要將可變物件宣告為 public static final

若物件是可變的,不要把它設為公開且為 final。否則所有人都能存取其內部狀態,總會有人真的去改它。

6. 初始化靜態與 final 欄位時的常見錯誤

錯誤 №1:未初始化的 final 欄位。 如果忘了在宣告時或在所有建構子中初始化 final 欄位,編譯器會報錯。例如若類別有兩個建構子,而你在其中一個建構子忘了為 final 欄位賦值——就會出現錯誤。

錯誤 №2:嘗試變更 final 欄位的值。 在初始化之後試圖修改 final 欄位會導致編譯錯誤。

public class Demo {
    private final int x = 5;
    public void change() {
        x = 10; // 錯誤: cannot assign a value to final variable x
    }
}

錯誤 №3:公開的可變 static final 欄位。 若將可變物件(如 String[]int[] 等)宣告為 public static final,任何程式碼都能修改其內容。這會破壞封裝,並可能導致難以追蹤的 bug。

錯誤 №4:在靜態區塊中使用非靜態欄位。 在靜態區塊中不能存取非靜態欄位,因為它們尚未被初始化(在該階段根本還不存在)。

錯誤 №5:出乎意料的初始化順序。 如果在靜態區塊中存取到於後面才宣告的靜態欄位,這些欄位可能尚未初始化。若要在靜態區塊中使用某些靜態欄位,請務必先於程式碼中宣告它們。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION