1. Trường static: khởi tạo như thế nào?
Trường tĩnh (static) không thuộc về một đối tượng cụ thể mà thuộc về toàn bộ lớp. Giá trị của nó chỉ có một bản cho tất cả các đối tượng của lớp đó. Có thể ví như “quỹ chung” cho toàn bộ nhân viên công ty — không quan trọng ai đến, quỹ đó là dùng chung cho mọi người.
Khởi tạo khi khai báo
Cách đơn giản và phổ biến nhất để khởi tạo trường static là gán giá trị cho nó ngay khi khai báo:
public class User {
private static int userCount = 0; // khởi tạo ngay tại đây
// ... phần mã còn lại
}
Trường này sẽ được khởi tạo khi lớp User lần đầu được nạp vào bộ nhớ.
Khởi tạo trong khối static
Đôi khi cần logic khởi tạo phức tạp hơn — ví dụ, tải dữ liệu từ tệp hoặc thực hiện tính toán. Khi đó, dùng khối khởi tạo tĩnh:
public class Config {
public static String configPath;
static {
// Khối này sẽ chạy MỘT lần khi nạp lớp
configPath = System.getenv("APP_CONFIG_PATH");
if (configPath == null) {
configPath = "/etc/app/default.conf";
}
System.out.println("Đường dẫn cấu hình đã được khởi tạo: " + configPath);
}
}
Khi nào khối static được gọi?
- Khi lần đầu tiên truy cập đến lớp (ví dụ: tạo đối tượng đầu tiên hoặc gọi bất kỳ phương thức/trường tĩnh nào).
- Chỉ được thực thi đúng một lần cho mỗi lớp.
Đặc điểm truy cập các trường static
- Truy cập các trường static qua tên lớp: User.userCount.
- Cũng có thể truy cập qua đối tượng, nhưng đó là phong cách không tốt (dễ gây rối cho người đọc mã).
Ví dụ:
User u1 = new User();
User u2 = new User();
System.out.println(User.userCount); // đúng
System.out.println(u1.userCount); // chạy được nhưng không khuyến nghị!
2. Trường final: khi nào và cách khởi tạo?
final là một bộ sửa đổi cho biết: “Trường này chỉ có thể được gán một lần và sau đó không thay đổi nữa”. Sau khi khởi tạo, giá trị của trường trở nên bất biến: với đối tượng — đó là thuộc tính cố định của nó, còn với lớp (nếu trường là static) — đó là một hằng số dùng chung.
Ví dụ sử dụng:
- Hằng số (ví dụ: PI).
- Mã định danh duy nhất của đối tượng, không thể thay đổi sau khi tạo.
Yêu cầu khởi tạo trường final
Trong Java có quy tắc nghiêm ngặt: mỗi trường final bắt buộc phải được khởi tạo hoặc khi khai báo, hoặc trong mọi constructor của lớp.
Khởi tạo khi khai báo
public class Circle {
public static final double PI = 3.1415926535; // hằng số của lớp
private final String id = "CIRCLE"; // hằng số của đối tượng
}
Khởi tạo trong constructor
Đôi khi giá trị của trường final chỉ được biết tại thời điểm tạo đối tượng:
public class User {
private final int id;
public User(int id) {
this.id = id; // gán trường final trong constructor
}
}
Quan trọng: nếu lớp có nhiều constructor, trường final phải được khởi tạo trong từng constructor!
Ví dụ kết hợp
public class Token {
private final String value;
private final long timestamp;
public Token(String value) {
this.value = value;
this.timestamp = System.currentTimeMillis();
}
}
Lỗi biên dịch khi khởi tạo không đúng
Nếu bạn quên khởi tạo một trường final, trình biên dịch sẽ không cho phép xây dựng chương trình:
public class Broken {
private final int x; // chưa được khởi tạo
public Broken() {
// x không được gán!
}
}
// Lỗi: variable x might not have been initialized
3. Kết hợp static và final: khai báo hằng số của lớp
Bộ ba public static final xuất hiện rất thường xuyên. Trong Java, đây là cách khai báo hằng số của lớp — các giá trị được đặt một lần và không thay đổi trong suốt quá trình chạy chương trình. Những hằng số này thuộc về toàn bộ lớp và giống nhau cho mọi đối tượng của lớp.
Cú pháp và ví dụ
public class MathUtils {
public static final double PI = 3.1415926535;
public static final String APP_NAME = "MyApp";
}
Giải thích:
- public — có thể truy cập ở mọi nơi.
- static — thuộc về toàn bộ lớp, không phải đối tượng riêng lẻ.
- final — không thể thay đổi sau khi khởi tạo.
Sử dụng
double area = MathUtils.PI * r * r;
System.out.println(MathUtils.APP_NAME);
Quy ước: tên hằng thường được viết HOA_CÓ_GẠCH_DƯỚI.
4. Ví dụ mã: các cách khởi tạo trường static và final
Ví dụ 1: Lớp đơn giản với hằng số
public class Constants {
public static final int DAYS_IN_WEEK = 7;
public static final String COMPANY = "LLC Romashka";
}
Ví dụ 2: Trường static được khởi tạo trong khối static
public class AppConfig {
public static final String DEFAULT_PATH;
static {
// Có thể thực hiện logic phức tạp
String env = System.getenv("APP_PATH");
if (env != null) {
DEFAULT_PATH = env;
} else {
DEFAULT_PATH = "/usr/local/app";
}
}
}
Ví dụ 3: Trường final của đối tượng, khởi tạo trong constructor
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;
}
}
Cách dùng:
User u1 = new User("Ivan");
User u2 = new User("Maria");
System.out.println(u1.getId()); // 1
System.out.println(u2.getId()); // 2
Ví dụ 4: Lỗi khi khởi tạo trường final không đúng
public class BadExample {
private final int number;
public BadExample() {
// number chưa được khởi tạo!
}
}
// Lỗi biên dịch: variable number might not have been initialized
5. Thực tiễn tốt khi làm việc với trường static và final
Chỉ dùng public static final cho các hằng số thực sự
Nếu giá trị có thể thay đổi trong tương lai (ví dụ: danh sách nhân viên), đừng đặt nó là final! Hằng số phải thực sự bất biến.
public static final int MAX_USERS = 1000; // tốt
public static final String[] USERS = new String[100]; // xấu!
Mặc dù chính tham chiếu USERS không thay đổi, nội dung mảng vẫn có thể bị sửa. Điều này có thể dẫn đến các lỗi khó lường.
Dùng các khối static cho khởi tạo phức tạp
Nếu hằng số không thể biểu diễn bằng một phép gán đơn giản (ví dụ: cần tính toán hoặc đọc từ tệp), hãy sử dụng khối static.
Đừng lạm dụng trường static
Các trường static là biến toàn cục. Quá nhiều trường static có thể dẫn đến lỗi khó phát hiện và việc bảo trì mã phức tạp.
Đừng để các đối tượng có thể thay đổi ở dạng public static final
Nếu đối tượng có thể thay đổi, đừng để nó ở dạng public và final. Điều đó sẽ mở quyền truy cập nội bộ của nó cho tất cả mọi người, và kiểu gì cũng sẽ có người lợi dụng điều đó.
6. Lỗi thường gặp khi khởi tạo các trường static và final
Lỗi №1: Trường final chưa được khởi tạo. Nếu quên khởi tạo trường final hoặc khi khai báo, hoặc trong mọi constructor, trình biên dịch sẽ báo lỗi. Ví dụ, nếu lớp có hai constructor nhưng bạn quên gán giá trị cho trường final trong một trong số đó — sẽ có lỗi.
Lỗi №2: Thay đổi giá trị của trường final. Cố gắng thay đổi giá trị của trường final sau khi khởi tạo sẽ dẫn đến lỗi biên dịch.
public class Demo {
private final int x = 5;
public void change() {
x = 10; // Lỗi: cannot assign a value to final variable x
}
}
Lỗi №3: public static final cho các trường có thể thay đổi. Nếu đặt một đối tượng có thể thay đổi (String[], int[], v.v.) là public static final, mọi mã đều có thể thay đổi nội dung của nó. Điều này vi phạm tính đóng gói và có thể gây ra các lỗi khó phát hiện.
Lỗi №4: Sử dụng trường không phải static trong khối static. Trong khối static không thể truy cập các trường không phải static, vì chúng chưa được khởi tạo (và thực tế là chưa tồn tại ở giai đoạn này).
Lỗi №5: Thứ tự khởi tạo ngoài mong đợi. Nếu trong khối static bạn truy cập các trường static được khai báo phía dưới trong mã, chúng có thể chưa được khởi tạo. Luôn khai báo các trường static trước các khối static nếu bạn dự định sử dụng chúng.
GO TO FULL VERSION