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:出乎意料的初始化順序。 如果在靜態區塊中存取到於後面才宣告的靜態欄位,這些欄位可能尚未初始化。若要在靜態區塊中使用某些靜態欄位,請務必先於程式碼中宣告它們。
GO TO FULL VERSION