Mutable và Immutable Objects trong Java: Giải thích Cặn Kẽ

Có thể bạn sẽ cảm thấy lạ lẫm khi mới nghe đến “Mutable” và “Immutable Objects”. Đừng lo lắng, đây không phải là một khái niệm quá phức tạp hay một kỹ thuật lập trình cao siêu nào cả. Thực tế, chúng là những khái niệm cơ bản, được sử dụng để mô tả đặc tính của các đối tượng trong lập trình hướng đối tượng (OOP), đặc biệt là trong Java.

Mutable và Immutable Objects xuất hiện thường xuyên trong quá trình bạn xây dựng các chương trình Java hoặc các chương trình OOP nói chung. Vậy, chính xác thì Mutable (có thể thay đổi) và Immutable (không thể thay đổi) Objects là gì?

Về cơ bản, cả Mutable và Immutable Objects đều là các Class do bạn tạo ra hoặc được cung cấp sẵn trong Java. Sự khác biệt giữa chúng không nằm ở tên Class, tính kế thừa hay cấu trúc phức tạp, mà ở đặc điểm và hành vi của đối tượng, được thể hiện qua các phương thức Getter và Setter.

Tóm lại, chúng ta có thể định nghĩa Mutable và Immutable Objects như sau:

  • Mutable Object (Đối tượng khả biến): Sau khi một đối tượng được khởi tạo, trạng thái của nó (các trường dữ liệu mà nó nắm giữ, ví dụ: tên, tuổi của một đối tượng sinh viên) có thể thay đổi được.

  • Immutable Object (Đối tượng bất biến): Sau khi một đối tượng được khởi tạo, trạng thái của nó không thể thay đổi được. Điều này có nghĩa là bạn chỉ có thể “get” (lấy) giá trị, mà không thể “set” (gán) giá trị mới.

Để hiểu rõ hơn, hãy xem xét ví dụ về cách xây dựng Mutable và Immutable Class trong Java.

1. Xây dựng Mutable Class (Lớp Khả Biến):

public class MutableClass {
    private String firstName;
    private String lastName;

    public MutableClass(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    // Mutator (Getter và Setter)
    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
}

Trong ví dụ trên, MutableClass có các phương thức setFirstNamesetLastName, cho phép bạn thay đổi giá trị của các thuộc tính firstNamelastName sau khi đối tượng đã được tạo. Do đó, đây là một Mutable Class.

2. Xây dựng Immutable Class (Lớp Bất Biến):

import java.util.Date;

public final class ImmutableClass {
    private final String firstName;
    private final String lastName;
    private final Date dateOfBirth;

    public ImmutableClass(String firstName, String lastName, Date dob) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.dateOfBirth = new Date(dob.getTime()); // Tạo bản sao để đảm bảo tính bất biến
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public Date getDateOfBirth() {
        return new Date(dateOfBirth.getTime()); // Trả về bản sao để đảm bảo tính bất biến
    }
}

Trong ví dụ này, ImmutableClass có một số điểm quan trọng:

  • final class: final đảm bảo class không thể bị kế thừa, ngăn chặn việc thay đổi hành vi thông qua kế thừa.
  • private final fields: Các thuộc tính firstName, lastName, và dateOfBirth được khai báo là privatefinal. Điều này có nghĩa là chúng chỉ có thể được khởi tạo một lần trong constructor và không thể thay đổi sau đó.
  • Không có setter: Không có phương thức “set” nào được cung cấp để thay đổi giá trị của các thuộc tính.
  • Bản sao defensive: Trong constructor và getter của dateOfBirth, chúng ta tạo một bản sao của đối tượng Date để đảm bảo rằng người dùng không thể thay đổi trạng thái bên trong của đối tượng ImmutableClass thông qua tham chiếu đến đối tượng Date ban đầu.

Ví dụ sử dụng:

import java.util.Date;

public class StudentRunner {
    public static void main(String[] args) {
        Date myDOB = new Date();
        ImmutableClass student = new ImmutableClass("Manh", "Tien", myDOB);

        System.out.println(student.getFirstName());
        // student.setFirstName("New Name"); // Lỗi: Không có phương thức setFirstName
    }
}

Đoạn code trên minh họa cách tạo một đối tượng ImmutableClass. Bạn có thể truy cập các thuộc tính bằng các phương thức get, nhưng bạn không thể thay đổi chúng.

Tại sao lại cần Immutable Objects?

Immutable Objects mang lại nhiều lợi ích quan trọng trong lập trình:

  • An toàn luồng (Thread-safe): Vì trạng thái của Immutable Objects không thể thay đổi, chúng an toàn khi sử dụng trong môi trường đa luồng. Bạn không cần lo lắng về việc đồng bộ hóa để bảo vệ dữ liệu.
  • Đơn giản hóa logic: Việc không cần lo lắng về việc trạng thái của đối tượng bị thay đổi bất ngờ giúp đơn giản hóa logic của chương trình và giảm thiểu lỗi.
  • Dễ dàng debug: Khi một đối tượng là immutable, bạn có thể chắc chắn rằng giá trị của nó sẽ không thay đổi trong suốt vòng đời của nó, giúp bạn dễ dàng theo dõi và debug chương trình hơn.
  • Sử dụng làm khóa (Key) trong HashMap: Immutable Objects rất phù hợp để sử dụng làm khóa trong HashMap vì giá trị hash của chúng sẽ không thay đổi.

Kết luận:

Hiểu rõ sự khác biệt giữa Mutable và Immutable Objects là một phần quan trọng trong việc viết code Java chất lượng cao. Việc lựa chọn sử dụng loại đối tượng nào phụ thuộc vào yêu cầu cụ thể của ứng dụng, nhưng nhìn chung, việc sử dụng Immutable Objects thường được khuyến khích vì những lợi ích mà nó mang lại về tính an toàn, dễ bảo trì và hiệu suất.