Khái niệm về immutable luôn chiếm một phần quan trọng trong nhiều ngôn ngữ lập trình ngày nay, Java không phải là ngoại lệ. Java 8 ra đời kèm theo functional programming và java.time.API khiến immutable càng trở nên quan trọng hơn.

#1 Immutable là gì?

Nếu định nghĩa một cách ngắn gọn, ta có immutable là một đặc tính của một đối tượng nào đó trong lập trình.

Ví dụ cụ thể với class. Một class được xếp loại là immutable class nếu các bản thể (instance) mà nó tạo ra không thể thay đổi được. Cụ thể, thông tin lưu trong object đó được gán ngay trong quá trình khởi tạo object, kể từ đó, ta không thể thay đổi thông tin cho object này. Nếu ta cần object đó mang giá trị khác, ta buộc phải làm một object mới.

#2 Ưu điểm của tính immutable

Nếu bạn chưa từng làm việc hoặc chưa nghe đến immutable, có lẽ bạn sẽ nghĩ nó là tính năng thừa. Tuy nhiên immutable mang đến rất nhiều lơị ích cho hệ thống. Cụ thể như sau:

Thứ nhất: hỗ trợ xây dựng hệ thống ổn định. Ưu điểm này đến từ chính đặc tính vốn có của Immutable là không thể thay đổi giá trị sau khi khởi tạo .

Lấy ví dụ về class Bank, đại diện cho một ngân hàng. Ta xét hoàn cảnh sau: khi cuộc khủng hoảng tài chính trôi qua, nhà băng không cho phép tài khoản người dùng có thẻ có giá trị số dư là âm. Để làm được điều này, người ta sẽ thêm một method kiểm tra và các luật sao cho khi có tài khoản bị âm, nó sẽ bắn ra IllegalArgumentException. Kiểu luật này gọi là invariant - luật bất biến.

public class BankAccount{

[...]

    private void validate(long balance) {

        if (balance < 0) {

            throw new IllegalArgumentException("balance must not be negative:"+ balance);

        }

    }

}

Trong một class thông thường, hàm validedate() có thể được gọi bất kì thời điểm nào nếu có xảy ra thay đổi số dư tài khoản. Tuy nhiên với immutable class, ta chỉ cần gọi validate() một lần duy nhất trong constructor.

public BankAccount(long balance) {

    validate(balance);

    this.balance = balance;

}

Không thể thay đổi giá trị của Immutable object. Điều này đúng với một object kể từ lúc nó được khởi tạo cho đến khi bị "dọn dẹp" bởi Garbage Collector. Mỗi khi số dư tài khoản bị thay đổi, một object mới sẽ được tạo ra. Vì thế ta chỉ cần kiểm tra giá trị tài khoản tại thời điểm object tương ứng được khởi tạo. Đồng nghĩa với gọi validate() một lần duy nhất trong constructor. Từ đó, ta có thể tập trung các luật bất biến trong ứng dụng mà ta đang xây dựng và đảm bảo tính nhất quán cho các object trong suốt vòng đời của chúng.

Thứ hai, tính immutable có thể được áp dụng cho các hệ thống đặc thù yêu cầu "khả năng chịu lỗi" (fault-tolerance).

Hãy tưởng tượng bạn cần rút tiền từ ngân hàng. Tại thời điểm mà tài khoản của bạn bị trừ đi số tiền mà ATM sắp nhả ra thì xuất hiện lỗi. Như vậy với normal class, bạn đã mất tiền. Nhưng với immutable class, lỗi sẽ được bắn ra kèm theo đó là tài khoản của bạn không hề thay đổi trừ khi bạn đã nhận được tiền.

public ImmutableAccount withdraw(long amount) {

    long newBalance = newBalance(amount);

    return new ImmutableAccount(newBalance);

}

private long newBalance(long amount) {

    // exception during balance calculation

}

Immutable object không bao giờ rơi vào trạng thái phi nhất quán (inconsistence), ngay cả khi xảy ra exception. Điều này góp phần tăng tính ổn định cho hệ thống.

Thứ ba, tính immutable có thể được chia sẻ giữa các object.

Ví dụ: ta có một object kiểu Account, trong object Account, có thuộc tính kiểu Holder và Balance (tất nhiên chúng cũng là object).

Khi ta copy object Account, ta có thể để 2 bản thể đó sử dụng chung thuộc tính Balance. Và nếu ta thay đổi Balance ở một object Account, nó sẽ không ảnh hưởng tới object Account còn lại. Object còn lại tạo một bản thể (vẫn giữ tính Immutable) của class Account và 2 object cũ - mới này không hề liên quan đến nhau nữa.

Một Immutable object không cần copy constructor khi copy object. Immutable object có thể được chia sẻ tự do khi sử dụng thuật toán lock-free trong môi trường multithread.

Cuối cùng, immutable object là một lựa chọn sáng giá khi sử dụng làm key của Map hoặc làm element của Set.

Tham khảo các khóa học lập trình online, onlab, và thực tập lập trình tại TechMaster

#3 Nhược điểm của tính Immutable

Nhược điểm lớn nhất của immutable là nó có nguy cơ tác động tiêu cực lên performance. Ta sẽ cần khởi tạo nhiều immutable object so với mutable object, và cứ mỗi object tạo ra thì tài nguyên dự trữ  của hệ thống lại vơi đi một chút.

Vì vậy, để tránh vấn đề đó ảnh hưởng tới quá trình xây dựng ứng dụng, ta cần khảo sát kỹ các yếu tố như: loại thiết bị triển khai ứng dụng, đặc điểm phần cứng của thiết bị đó, kích thước của ứng dụng... Kết quả khảo sát tổng hợp từ các yếu tố này sẽ giúp bạn có quyết định đúng đắn về việc có nên sử dụng immutable object hay không.

#4 Áp dụng

Bây giờ tôi sẽ đưa ra một ví dụ để các bạn thấy tầm quan trọng của immutable trong thực tế và cách tạo ra immutable class.

    public String name;

    public Destination destination;

    public Spaceship(String name) {

        this.name = name;

        this.destination = Destination.NONE;

    }

    public Spaceship(String name, Destination destination) {

        this.name = name;

        this.destination = destination;

    }

    public Destination currentDestination() {

        return destination;

    }

    public Spaceship exploreGalaxy() {

        destination = Destination.OUTER_SPACE;

    }

[…]

}

Để hô biến một class từ Mutable thành Immutable, ta cần thực hienj 4 bước:

  1. Đặt tất cả các trường (field) thành private và final
  2. Loại bỏ các method làm thay đổi state của object
  3. Đảm bảo class hiện tại không thể extend được
  4. Đảm bảo truy cập độc quyền tới mutable field (các trường có tính chất mutable)

Lý thuyết là vậy, chúng ta hãy áp dụng nó vào ví dụ trên, từng bước một.

Bước 1:

Thêm modifier là private giúp các trường không thể bị thay đổi bởi các class nằm ngoài class chứa nó.

Thêm final nhằm mục đích ngăn các tác nhân bên ngoài gán giá trị cho các trường trong class này thông qua biến tham chiếu (nếu có ai đó cố ý làm thế, tất nhiên sẽ xảy ra lỗi)

Bước 2:

Tiếp theo, ta cần "bảo vệ" các object state khỏi sự thay đổi.

public ImmutableSpaceship exploreGalaxy() {

    return new ImmutableSpaceship(name, Destination.OUTER_SPACE);

}

Bất kỳ trường nào mà ta không thay đổi thì giá trị của chúng đều có thể được copy từ object hiện thời. Nếu có thay đổi, object mới sẽ được khởi tạo.

Bước 3:

Để ngăn cản sự thay đổi của class, ta không được phép cho các class khác extend class hiện thời. Lý do: các class con có thể Override các method ở class hiện thời và các method được Override đó hoàn toàn có thể thay đổi object tạo bởi class hiện thời, ví dụ luôn:

public class EvilSpaceship extends Spaceship {

    [...]

    @Override

    public EvilSpaceship exploreGalaxy() {

        this.destination = Destination.OUTER_SPACE;

        return this;

    }

}

Để tránh điều này xảy ra, ta thêm từ khóa final vào phần khai báo class:

public final class Spaceship

Bước 4:

Bước cuối cùng này có vai trò ngăn chặn các truy cập trực tiếp tới mutable field. Cụ thể, chúng ta không nên return các tham chiếu trực tiếp tới Destination object. Thay vào đó, cần tạo một bản copy của mutable object và làm việc với nó.

Với ví dụ trên, ta cần check sự hiện diện của các tham chiếu tới Destination tại các public method và constructor...

Ở method curentDestination, ra thấy object Destination được return - đây chính là vấn đề. Thay vì return tham chiếu thật, hãy tạo bản copy của Destination object và trả về reference trỏ đến bản copy.

public Destination currentDestination() {

    return new Destination(destination);

}
private ImmutableSpaceship(String name, Destination destination) {

    this.name = name;

    this.destination = new Destination(destination);

}

Sau 4 bước, ta thu được sản phẩm:

public final class ImmutableSpaceship {

    private final String name;

    private final Destination destination;

    public ImmutableSpaceship(String name) {

        this.name = name;

        this.destination = new Destination("NONE");

    }

    private ImmutableSpaceship(String name, Destination destination) {

        this.name = name;

        this.destination = new Destination(destination);

    }

    public Destination currentDestination() {

        return new Destination(destination);

    }

    public ImmutableSpaceship newDestination(Destination newDestination) {

        return new ImmutableSpaceship(this.name, newDestination);

    }

[…]

}

Xem thêm phần thảo luận tại đây.

Techmaster via Dzone