Việc khởi tạo lớp (class initialization) trong Java có thể là một chủ đề phức tạp, nhưng hiểu rõ khi nào và cách các lớp được khởi tạo là điều cần thiết để làm chủ ngôn ngữ Java. Theo Đặc tả Máy Ảo Java (Java Virtual Machine Specification) – Chương 5.5 về Khởi tạo, có chính xác 6 tình huống khiến một lớp phải được khởi tạo ngay lập tức:
Các tình huống kích hoạt việc khởi tạo lớp
1. Gặp phải các câu lệnh bytecode sau:
new
(tạo một thể hiện đối tượng)getstatic
(đọc một trường tĩnh không được đánh dấufinal
)putstatic
(gán giá trị cho một trường tĩnh không được đánh dấufinal
)invokestatic
(gọi một phương thức tĩnh)
2. Sử dụng các phương thức phản chiếu (reflection
) từ gói java.lang.reflect
.
3. Khi lớp cha (superclass) chưa được khởi tạo.
4. Khi JVM bắt đầu thực thi và một lớp chính (main class
) được chỉ định.
5. Khi sử dụng hỗ trợ ngôn ngữ động trong Java 7+ và MethodHandle
được phân giải đến một trong các dạng sau:
REF_getStatic
REF_putStatic
REF_invokeStatic
REF_newInvokeSpecial
6. Một interface định nghĩa phương thức mặc định (default method
), và lớp triển khai interface đó được khởi tạo đồng thời.
Các tình huống khởi tạo lớp với ví dụ
1. Lớp con tham chiếu đến lớp cha
Việc tham chiếu đến một trường tĩnh của lớp cha thông qua lớp con không khởi tạo lớp con:
class Super { static { System.out.println("Super"); } public static int value = 1;
} class Sub extends Super { static { System.out.println("Sub"); }
} public class Main { public static void main(String[] args) { System.out.println(Sub.value); }
}
Kết quả:
Super
1
Giải thích:
- Chỉ lớp
Super
được khởi tạo vì trườngvalue
thuộc về nó. Việc truy cậpSub.value
chỉ kích hoạt khởi tạo lớp chaSuper
.
2. Tạo mảng không kích hoạt khởi tạo lớp
Việc tạo một mảng của một lớp không dẫn đến khởi tạo lớp đó:
public class Main { public static void main(String[] args) { Sub[] array = new Sub[5]; }
}
Kết quả:
- Không có đầu ra, tức là lớp
Sub
không được khởi tạo.
Giải thích:
- Tham chiếu mảng không làm kích hoạt việc khởi tạo lớp. JVM chỉ tạo một lớp đặc biệt với tên là
"[Lcom.example.demo.Sub"
để biểu diễn mảng một chiều, nhưng không khởi tạo bản thân lớp Sub.
3. Tham Chiếu Hằng Số Trong Constant Pool
Nếu một hằng số được khai báo là final
, nó sẽ được chèn trực tiếp vào constant pool của lớp gọi tại thời điểm biên dịch, do đó tránh việc khởi tạo lớp chứa hằng:
class Super { static { System.out.println("Super"); } public static final String HELLO = "Hello";
} public class Main { public static void main(String[] args) { System.out.println(Super.HELLO); }
}
Kết quả:
Hello
Giải thích:
- Vì hằng số
HELLO
được biên dịch và chèn trực tiếp vào lớpMain
, nên lớpSuper
không bị khởi tạo.
4. Quy tắc khởi tạo Interface
Khác với lớp, interface không khởi tạo tất cả interface cha cùng lúc. Chúng chỉ được khởi tạo khi thật sự cần thiết. Ngoài ra, interface không thể có các khối static block
, nhưng có thể có phương thức khởi tạo tĩnh ẩn (phương thức <clinit>
) để khởi tạo các biến.
Kết luận
Hiểu rõ các tình huống cụ thể trong quá trình khởi tạo lớp giúp bạn kiểm soát hành vi khởi tạo trong Java, từ đó tối ưu hóa thời gian khởi động ứng dụng và tránh những hành vi không mong đợi. Việc nắm bắt những kiến thức này đặc biệt quan trọng khi xây dựng các hệ thống lớn hoặc tối ưu hiệu suất khởi chạy.
Cảm ơn các bạn đã theo dõi!