Bỏ qua để đến nội dung

Static Initialization Order

Các field static được khởi tạo theo đúng thứ tự khai báo từ trên xuống trong file - nếu một field static tham chiếu đến một field static khác được khai báo phía sau nó, nó sẽ nhận giá trị mặc định (0, null,…) thay vì giá trị mong đợi:

public class Config {
static int doubled = base * 2; // base CHƯA được khởi tạo ở đây! -> lấy giá trị mặc định 0
static int base = 10; // base được khởi tạo SAU dòng trên
public static void main(String[] args) {
System.out.println(Config.doubled); // 0 - KHÔNG phải 20!
System.out.println(Config.base); // 10
}
}

JVM khởi tạo các field static tuần tự theo thứ tự xuất hiện trong file, giống như một khối lệnh chạy từ trên xuống (thực chất chúng được gộp vào một static initializer block ẩn). Tại thời điểm dòng static int doubled = base * 2; chạy, base chưa được gán 10 - nó vẫn đang ở giá trị mặc định 0 (giá trị mặc định ban đầu của mọi field static kiểu số trước khi initializer chạy tới nó).

Trường hợp phức tạp hơn: giữa nhiều class

Phần tiêu đề “Trường hợp phức tạp hơn: giữa nhiều class”

Thứ tự khởi tạo static giữa các class khác nhau phụ thuộc vào thứ tự class nào được JVM load trước (thường là khi lần đầu được tham chiếu đến) - điều này có thể khó dự đoán và gây ra hiện tượng gọi là “static initialization order fiasco”, đặc biệt khi các static field của các class khác nhau phụ thuộc chéo lẫn nhau.

public class A {
static int value = B.getValue() + 1; // B có được load và khởi tạo trước A hay chưa?
}
public class B {
static int getValue() { return 10; }
}

1. Khai báo field static theo đúng thứ tự phụ thuộc - field bị phụ thuộc phải khai báo trước field phụ thuộc vào nó:

public class Config {
static int base = 10; // khai báo TRƯỚC
static int doubled = base * 2; // giờ base đã có giá trị đúng khi dòng này chạy
public static void main(String[] args) {
System.out.println(Config.doubled); // 20 - ĐÚNG
}
}

2. Dùng static initializer block khi logic khởi tạo phức tạp, cần nhiều bước:

public class Config {
static int base;
static int doubled;
static { // static initializer block - chạy MỘT LẦN khi class được load
base = 10;
doubled = base * 2;
}
}

3. Tránh để các static field của các class khác nhau phụ thuộc chéo lẫn nhau - nếu cần chia sẻ trạng thái khởi tạo phức tạp, cân nhắc dùng một phương thức khởi tạo tường minh được gọi rõ ràng từ main, thay vì dựa vào thứ tự load class ngầm định.

Static field được khởi tạo tuần tự theo thứ tự khai báo trong file - tham chiếu đến static field khai báo phía sau sẽ nhận giá trị mặc định, không phải giá trị đã gán.

  • Luôn khai báo field bị phụ thuộc trước field phụ thuộc vào nó
  • Dùng static initializer block (static { ... }) cho logic khởi tạo nhiều bước phức tạp
  • Tránh phụ thuộc chéo giữa static field của các class khác nhau - thứ tự load class có thể khó dự đoán