CodeGym /Các khóa học /JAVA 25 SELF /Khối khởi tạo

Khối khởi tạo

JAVA 25 SELF
Mức độ , Bài học
Có sẵn

1. Khối khởi tạo là gì

Trong Java có hai loại khối khởi tạo:

  • Khối khởi tạo không tĩnh (thông thường) — được thực thi mỗi lần tạo một đối tượng mới, ngay sau khi các trường được khởi tạo và trước khi gọi constructor.
  • Khối khởi tạo tĩnh — được thực thi một lần khi lớp được nạp vào bộ nhớ (trước khi tạo đối tượng đầu tiên hoặc truy cập vào trường/phương thức tĩnh).

Khối khởi tạo không tĩnh

Khai báo trực tiếp trong thân lớp, không có từ khóa như static:

public class User {
    private String name;

    // Khối khởi tạo không tĩnh
    {
        System.out.println("Đang thực thi khối không tĩnh!");
        name = "Tên mặc định";
    }

    public User() {
        System.out.println("Đang thực thi constructor!");
    }
}

Khối khởi tạo tĩnh

Khai báo với từ khóa static:

public class Config {
    public static String appName;

    static {
        System.out.println("Đang thực thi khối static!");
        appName = "Ứng dụng siêu cấp của tôi";
    }
}

2. Thứ tự khởi tạo: ai đi trước?

Trong Java, thứ tự khởi tạo các thành phần của lớp không phải “ngẫu hứng”, mà là một trình tự được xác định chặt chẽ. Nếu bạn từng thử lắp đồ nội thất IKEA mà không có hướng dẫn, bạn sẽ hiểu vì sao thứ tự này quan trọng: làm sai bước, tủ quần áo sẽ thành một tác phẩm nghệ thuật.

Thứ tự khởi tạo:

  1. Trường tĩnh và khối tĩnh — theo thứ tự chúng được khai báo trong lớp. Chạy một lần khi lớp được nạp.
  2. Trường không tĩnh và khối không tĩnh — theo thứ tự khai báo, mỗi khi tạo một đối tượng mới.
  3. Constructor — chạy sau tất cả các khởi tạo không tĩnh.

Sơ đồ

+-------------------------------+
|   Nạp lớp vào JVM             |
+-------------------------------+
| 1. Trường tĩnh                |
| 2. Khối tĩnh                  |
|         ↓                     |
|   Tạo đối tượng               |
|         ↓                     |
| 3. Trường không tĩnh          |
| 4. Khối không tĩnh            |
| 5. Constructor                |
+-------------------------------+

Ví dụ với output

Hãy viết một lớp cho thấy mọi thứ diễn ra theo thứ tự nào:

public class Demo {
    static String staticField = print("1. trường static");

    static {
        print("2. khối static");
    }

    String field = print("3. trường không tĩnh");

    {
        print("4. khối không tĩnh");
    }

    public Demo() {
        print("5. constructor");
    }

    static String print(String msg) {
        System.out.println(msg);
        return msg;
    }

    public static void main(String[] args) {
        System.out.println("Tạo đối tượng Demo đầu tiên:");
        Demo d1 = new Demo();

        System.out.println("\nTạo đối tượng Demo thứ hai:");
        Demo d2 = new Demo();
    }
}

Màn hình sẽ hiển thị gì?

1. trường static
2. khối static
Tạo đối tượng Demo đầu tiên:
3. trường không tĩnh
4. khối không tĩnh
5. constructor

Tạo đối tượng Demo thứ hai:
3. trường không tĩnh
4. khối không tĩnh
5. constructor

Lưu ý: các phần tĩnh (static) chỉ chạy một lần — khi lần đầu tiên truy cập vào lớp. Mọi thứ không phải static sẽ chạy mỗi lần tạo đối tượng.

3. Ví dụ mã: khối khởi tạo dùng để làm gì

Khi constructor là chưa đủ

Đôi khi một phần khởi tạo phải dùng chung cho tất cả constructor. Ví dụ, lớp có nhiều constructor và bạn không muốn lặp lại cùng một đoạn khởi tạo trong từng cái. Khi đó, tiện nhất là đưa phần này vào một khối không tĩnh:

public class Person {
    private String id;
    private String name;

    {
        // Đoạn này chạy trước mọi constructor
        id = java.util.UUID.randomUUID().toString();
        System.out.println("Tạo id duy nhất: " + id);
    }

    public Person() {
        System.out.println("Person() không tham số");
    }

    public Person(String name) {
        this.name = name;
        System.out.println("Person(String name)");
    }
}

Kết quả: khi tạo bất kỳ đối tượng Person nào, id sẽ được sinh ra trước, sau đó constructor tương ứng sẽ chạy.

Khởi tạo dữ liệu tĩnh phức tạp

Khối static thường được dùng để khởi tạo các trường tĩnh “nặng” hoặc phức tạp, chẳng hạn đọc cấu hình từ tệp, tạo collection, kết nối cơ sở dữ liệu, v.v.

public class Settings {
    public static final java.util.Map<String, String> DEFAULTS;

    static {
        DEFAULTS = new java.util.HashMap<>();
        DEFAULTS.put("theme", "light");
        DEFAULTS.put("language", "ru");
        System.out.println("Khối static của Settings: thiết lập mặc định");
    }
}

4. Những lưu ý hữu ích

Khi nào nên dùng khối khởi tạo

Nên dùng khi

  • Để khởi tạo dùng chung cần trong mọi constructor.
  • Cho dữ liệu tĩnh phức tạp không thể biểu đạt bằng phép gán đơn giản.
  • Để khởi tạo tài nguyên tĩnh (ví dụ đọc tệp cấu hình khi ứng dụng khởi động).

Khi KHÔNG nên dùng

  • Nếu có thể dùng phép gán thông thường hoặc constructor — hãy dùng chúng.
  • Đừng “giấu” logic nghiệp vụ trong các khối khởi tạo — điều đó làm code khó đọc và khó bảo trì.
  • Nếu việc khởi tạo phụ thuộc tham số constructor, hãy thực hiện trong chính constructor.

Đừng lạm dụng khối khởi tạo

Các khối khởi tạo là công cụ mạnh nhưng không phải thường dùng. Phần lớn trường hợp chỉ cần phép gán đơn giản hoặc constructor. Nếu một lớp có quá nhiều khối khởi tạo, mã sẽ khó đọc và khó bảo trì.

Không dùng khối không tĩnh cho logic phụ thuộc tham số của constructor

Trong khối không tĩnh, bạn không thể dùng tham số của constructor vì nó chạy TRƯỚC constructor. Nếu cần khởi tạo dựa trên tham số, hãy làm ngay trong constructor.

Khối static và kế thừa

Các khối khởi tạo static không được thừa kế. Mỗi lớp có khối static của riêng nó. Khi nạp lớp con, khối static của lớp cơ sở chạy trước, rồi đến khối static của lớp con.

5. Những lỗi thường gặp khi làm việc với khối khởi tạo

Lỗi số 1: Kỳ vọng khối không tĩnh nhìn thấy tham số của constructor.
Nhiều người mới cố dùng tham số constructor trong khối không tĩnh và nhận lỗi biên dịch hoặc kết quả bất ngờ. Hãy nhớ: khối không tĩnh chạy TRƯỚC constructor, vì vậy lúc đó chưa có tham số nào.

Lỗi số 2: Nhồi quá nhiều logic vào các khối khởi tạo.
Khi logic phức tạp xuất hiện trong các khối này, mã sẽ rối rắm. Tốt hơn hãy đặt phần chính trong constructor hoặc các phương thức riêng.

Lỗi số 3: Nhiều khối static với thứ tự khác nhau.
Nếu lớp có nhiều khối static, chúng chạy theo thứ tự khai báo trong mã, cùng với các trường static. Đôi khi điều này gây ra kết quả bất ngờ, ví dụ khi một khối phụ thuộc vào kết quả của khối khác.

Lỗi số 4: Nghĩ rằng khối static sẽ chạy mỗi lần tạo đối tượng.
static chỉ chạy một lần — khi lớp được nạp. Nếu bạn trông chờ khởi tạo lại nhiều lần, điều đó sẽ không xảy ra.

Lỗi số 5: Cố truy cập trường không tĩnh từ khối static.
Trong khối static, chỉ có thể dùng biến và phương thức tĩnh. Cố truy cập các trường không tĩnh (thông thường) sẽ gây lỗi biên dịch.

1
Nhiệm vụ
JAVA 25 SELF, mức độ, bài học
Đã khóa
Lời chào đầu tiên từ module được nạp 🚀
Lời chào đầu tiên từ module được nạp 🚀
1
Nhiệm vụ
JAVA 25 SELF, mức độ, bài học
Đã khóa
Quy trình đăng ký thú cưng tại trại cứu hộ 🐾
Quy trình đăng ký thú cưng tại trại cứu hộ 🐾
1
Nhiệm vụ
JAVA 25 SELF, mức độ, bài học
Đã khóa
Giải mã thứ tự khởi chạy hệ thống 🧩
Giải mã thứ tự khởi chạy hệ thống 🧩
1
Nhiệm vụ
JAVA 25 SELF, mức độ, bài học
Đã khóa
Gán ID tự động khi đăng ký người dùng 🧑‍💻
Gán ID tự động khi đăng ký người dùng 🧑‍💻
1
Khảo sát/đố vui
, cấp độ , bài học
Không có sẵn
Đóng gói (Encapsulation)
Nguyên tắc đóng gói
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION