Trang chủHướng dẫnCách tạo một lớp immutable trong Java (Hướng dẫn từng bước)
Java

Cách tạo một lớp immutable trong Java (Hướng dẫn từng bước)

CyStack blog 6 phút để đọc
CyStack blog13/06/2025
Locker Avatar

Chris Pham

Technical Writer

Locker logo social
Reading Time: 6 minutes

Tạo lớp immutable trong Java

Bài viết này sẽ cung cấp cho bạn một bức tranh tổng quan về cách tạo một lớp immutable trong Java .

Một đối tượng (object) được coi là bất biến (immutable) khi trạng thái của nó không thay đổi sau khi được khởi tạo. Ví dụ, String là một lớp bất biến, và một khi đã được khởi tạo, giá trị của một object String sẽ không bao giờ thay đổi.

Vì một object bất biến không thể được cập nhật, bạn cần tạo một object mới cho mỗi lần thay đổi trạng thái. Tuy nhiên, các object bất biến cũng mang lại những lợi ích sau:

  • Sử dụng lớp bất biến rất có lợi cho bộ nhớ đệm (caching), bởi vì bạn không phải bận tâm về việc dữ liệu bị thay đổi.
  • Một lớp bất biến cũng an toàn cho luồng (thread-safe), nên bạn sẽ không cần lo lắng về vấn đề an toàn luồng trong môi trường đa luồng.

Tạo một lớp immutable trong Java

Để tạo một lớp immutable trong Java, bạn cần tuân thủ các nguyên tắc chung sau:

  1. Khai báo lớp làfinal: Điều này ngăn không cho bất kỳ lớp nào khác kế thừa (extend) nó.
  2. Đặt tất cả các trường (fields) làprivate: Để không ai có thể truy cập và thay đổi giá trị trực tiếp từ bên ngoài.
  3. Không cung cấp các phương thức setter cho các biến.
  4. Đặt tất cả các trường có thể thay đổi (mutable fields) làfinal: Đảm bảo rằng giá trị của một trường chỉ được gán duy nhất một lần.
  5. Khởi tạo tất cả các trường trong phương thức khởi tạo (constructor) và thực hiện sao chép sâu (deep copy).
  6. Thực hiện sao chép (cloning) các object trong các phương thức getter để trả về một bản sao thay vì trả về tham chiếu object thực tế.

Lớp sau đây là một ví dụ minh họa các nguyên tắc cơ bản của tính bất biến.

Lớp FinalClassExample định nghĩa các trường và cung cấp phương thức khởi tạo sử dụng sao chép sâu để khởi tạo object. Đoạn code trong phương thức main của tệp FinalClassExample.java kiểm tra tính bất biến của object.

Tạo một tệp mới có tên FinalClassExample.java và sao chép đoạn code sau:

FinalClassExample.java

`import java.util.HashMap; import java.util.Iterator;

public final class FinalClassExample { // [cite: 21] // fields of the FinalClassExample class private final int id; // [cite: 22] private final String name; // [cite: 22] private final HashMap<String, String> testMap; // [cite: 22]

public int getId() { // [cite: 22]
    return id;
}

public String getName() { // [cite: 23]
    return name;
}

// Getter function for mutable objects
public HashMap<String, String> getTestMap() { // [cite: 25]
    return (HashMap<String, String>) testMap.clone(); // [cite: 25]
}

// Constructor method performing deep copy
public FinalClassExample(int i, String n, HashMap<String, String> hm) { // [cite: 26]
    System.out.println("Performing Deep Copy for Object initialization"); // [cite: 26]
    // "this" keyword refers to the current object
    this.id = i; // [cite: 26]
    this.name = n; // [cite: 26]

    HashMap<String, String> tempMap = new HashMap<String, String>(); // [cite: 27]
    String key; // [cite: 27]
    Iterator<String> it = hm.keySet().iterator(); // [cite: 27]
    while (it.hasNext()) { // [cite: 27]
        key = it.next(); // [cite: 27]
        tempMap.put(key, hm.get(key)); // [cite: 27]
    }
    this.testMap = tempMap; // [cite: 27]
}

// Test the immutable class
public static void main(String[] args) { // [cite: 28]
    HashMap<String, String> h1 = new HashMap<String, String>(); // [cite: 28]
    h1.put("1", "first"); // [cite: 28]
    h1.put("2", "second"); // [cite: 28]

    String s = "original"; // [cite: 28]
    int i = 10; // [cite: 28]

    FinalClassExample ce = new FinalClassExample(i, s, h1); // [cite: 29]

    // print the ce values
    System.out.println("ce id: " + ce.getId()); // [cite: 30]
    System.out.println("ce name: " + ce.getName()); // [cite: 30]
    System.out.println("ce testMap: " + ce.getTestMap()); // [cite: 30]

    // change the local variable values
    i = 20; // [cite: 30]
    s = "modified"; // [cite: 30]
    h1.put("3", "third"); // [cite: 32]

    // print the values again
    System.out.println("ce id after local variable change: " + ce.getId()); // [cite: 32]
    System.out.println("ce name after local variable change: " + ce.getName()); // [cite: 32]
    System.out.println("ce testMap after local variable change: " + ce.getTestMap()); // [cite: 32]

    HashMap<String, String> hmTest = ce.getTestMap(); // [cite: 32]
    hmTest.put("4", "new"); // [cite: 32]

    System.out.println("ce testMap after changing variable from getter method: " + ce.getTestMap()); // [cite: 32]
}

}`

Biên dịch và chạy chương trình:

$ javac FinalClassExample.java $ java FinalClassExample

Lưu ý: Bạn có thể nhận được thông báo sau khi biên dịch tệp như sau: FinalClassExample.java uses unchecked or unsafe operations because the getter method is using an unchecked cast from HashMap<String, String> to Object Với ví dụ này, bạn cứ yên tâm bỏ qua cảnh báo của trình biên dịch nhé.

Bạn sẽ nhận được kết quả sau:

Output

Performing Deep Copy for Object initialization ce id: 10 ce name: original ce testMap: {1=first, 2=second} ce id after local variable change: 10 ce name after local variable change: original ce testMap after local variable change: {1=first, 2=second} ce testMap after changing variable from getter methods: {1=first, 2=second}

Đầu ra cho thấy các giá trị của HashMap không thay đổi vì hàm tạo sử dụng sao chép sâu và hàm getter trả về một bản sao của object gốc.

Điều gì xảy ra khi bạn không sử dụng sao chép sâu (deep copy) và nhân bản (cloning)

Bạn có thể thay đổi tệp FinalClassExample.java để xem điều gì xảy ra khi bạn sử dụng sao chép nông (shallow copy) thay vì sao chép sâu và trả về object thay vì một bản sao. Object sẽ không còn bất biến và có thể bị thay đổi. Thử thay đổi tệp ví dụ như sau (hoặc sao chép và dán đoạn code mẫu):

  • Xóa phương thức khởi tạo cung cấp sao chép sâu và thêm phương thức khởi tạo cung cấp sao chép nông được đánh dấu trong ví dụ sau.
  • Trong hàm getter, xóa return (HashMap<String, String>) testMap.clone(); và thêm return testMap;.

Tệp ví dụ của bạn sẽ có dạng như thế này:

FinalClassExample.java

`import java.util.HashMap; import java.util.Iterator;

public final class FinalClassExample { // [cite: 43] // fields of the FinalClassExample class private final int id; // [cite: 44] private final String name; // [cite: 44] private final HashMap<String, String> testMap; // [cite: 44]

public int getId() { // [cite: 44]
    return id;
}

public String getName() { // [cite: 45]
    return name;
}

// Getter function for mutable objects
public HashMap<String, String> getTestMap() { // [cite: 45]
    return testMap; // [cite: 47]
}

//Constructor method performing shallow copy
public FinalClassExample(int i, String n, HashMap<String, String> hm) { // [cite: 47]
    System.out.println("Performing Shallow Copy for Object initialization"); // [cite: 47]
    this.id = i; // [cite: 48]
    this.name = n; // [cite: 48]
    this.testMap = hm; // [cite: 48]
}

// Test the immutable class
public static void main(String[] args) { // [cite: 49]
    HashMap<String, String> h1 = new HashMap<String, String>(); // [cite: 49]
    h1.put("1", "first"); // [cite: 49]
    h1.put("2", "second"); // [cite: 49]

    String s = "original"; // [cite: 49]
    int i = 10; // [cite: 49]

    FinalClassExample ce = new FinalClassExample(i, s, h1); // [cite: 49]

    // print the ce values.
    System.out.println("ce id: " + ce.getId()); // [cite: 50]
    System.out.println("ce name: " + ce.getName()); // [cite: 50]
    System.out.println("ce testMap: " + ce.getTestMap()); // [cite: 50]

    // change the local variable values
    i = 20; // [cite: 50]
    s = "modified"; // [cite: 50]
    h1.put("3", "third"); // [cite: 50]

    // print the values again
    System.out.println("ce id after local variable change: " + ce.getId()); // [cite: 50]
    System.out.println("ce name after local variable change: " + ce.getName()); // [cite: 50]
    System.out.println("ce testMap after local variable change: " + ce.getTestMap()); // [cite: 50]

    HashMap<String, String> hmTest = ce.getTestMap(); // [cite: 50]
    hmTest.put("4", "new"); // [cite: 50]

    System.out.println("ce testMap after changing variable from getter method: " + ce.getTestMap()); // [cite: 50]
}

}`

Biên dịch và chạy chương trình:

javac FinalClassExample.javajava FinalClassExample

Bạn sẽ nhận được output sau:

Output

Performing Shallow Copy for Object initialization ce id: 10 ce name: original ce testMap: {1=first, 2=second} ce id after local variable change: 10 ce name after local variable change: original ce testMap after local variable change: {1=first, 2=second, 3=third} ce testMap after changing variable from getter methods: {1=first, 2=second, 3=third, 4=new}

Output này cho thấy các giá trị của HashMap đã thay đổi. Điều này là do phương thức khởi tạo sử dụng sao chép nông (shallow copy) và có một tham chiếu trực tiếp đến object gốc trong hàm getter.

Kết luận

Bạn đã tìm hiểu một số nguyên tắc chung cần tuân thủ khi tạo một lớp immutable trong Java, bao gồm tầm quan trọng của sao chép sâu (deep copy). Việc tạo các lớp bất biến trong Java là một phương thức giúp nâng cao tính bảo mật, độ tin cậy và khả năng xử lý dễ dàng trong môi trường đa luồng. Bằng cách tuân thủ các nguyên tắc đã trình bày như trên, bạn có thể xây dựng nên một hệ thống ổn định hơn.

0 Bình luận

Đăng nhập để thảo luận

Chuyên mục Hướng dẫn

Tổng hợp các bài viết hướng dẫn, nghiên cứu và phân tích chi tiết về kỹ thuật, các xu hướng công nghệ mới nhất dành cho lập trình viên.

Đăng ký nhận bản tin của chúng tôi

Hãy trở thành người nhận được các nội dung hữu ích của CyStack sớm nhất

Xem chính sách của chúng tôi Chính sách bảo mật.

Đăng ký nhận Newsletter

Nhận các nội dung hữu ích mới nhất