Lớp bất biến?


86

Làm thế nào người ta có thể tạo ra một lớp Java là bất biến, nhu cầu của tính bất biến là gì và có lợi ích gì khi sử dụng điều này không?


2
Có một giới thiệu cho các lớp học bất biến: javapractices.com/topic/...
qrtt1

kiểm tra @Immutablechú thích này từ jcabi-khía cạnh
yegor256

Câu trả lời:


135

Đối tượng bất biến là gì?

Đối tượng bất biến là đối tượng sẽ không thay đổi trạng thái sau khi nó được khởi tạo.

Làm thế nào để làm cho một đối tượng bất biến?

Nói chung, một đối tượng bất biến có thể được thực hiện bằng cách xác định một lớp mà không có bất kỳ thành viên nào của nó được hiển thị và không có bất kỳ bộ thiết lập nào.

Lớp sau sẽ tạo một đối tượng không thay đổi:

class ImmutableInt {
  private final int value;

  public ImmutableInt(int i) {
    value = i;
  }

  public int getValue() {
    return value;
  }
}

Như có thể thấy trong ví dụ trên, giá trị của ImmutableIntchỉ có thể được thiết lập khi đối tượng được khởi tạo, và bằng cách chỉ có getter ( getValue), trạng thái của đối tượng không thể thay đổi sau khi khởi tạo.

Tuy nhiên, cần phải lưu ý rằng tất cả các đối tượng được tham chiếu bởi đối tượng cũng phải là bất biến, hoặc có thể thay đổi trạng thái của đối tượng.

Ví dụ: cho phép một tham chiếu đến một mảng hoặc ArrayListlấy thông qua một getter sẽ cho phép trạng thái bên trong thay đổi bằng cách thay đổi mảng hoặc tập hợp:

class NotQuiteImmutableList<T> {
  private final List<T> list;

  public NotQuiteImmutableList(List<T> list) {
    // creates a new ArrayList and keeps a reference to it.
    this.list = new ArrayList(list); 
  }

  public List<T> getList() {
    return list;
  }
}

Vấn đề với đoạn mã trên là ArrayListcó thể lấy được getListvà bị thao túng, dẫn đến trạng thái của bản thân đối tượng bị thay đổi, do đó, không phải là bất biến.

// notQuiteImmutableList contains "a", "b", "c"
List<String> notQuiteImmutableList= new NotQuiteImmutableList(Arrays.asList("a", "b", "c"));

// now the list contains "a", "b", "c", "d" -- this list is mutable.
notQuiteImmutableList.getList().add("d");

Một cách để giải quyết vấn đề này là trả về bản sao của mảng hoặc tập hợp khi được gọi từ getter:

public List<T> getList() {
  // return a copy of the list so the internal state cannot be altered
  return new ArrayList(list);
}

Ưu điểm của tính bất biến là gì?

Lợi thế của tính bất biến đi kèm với tính đồng thời. Rất khó để duy trì tính đúng đắn trong các đối tượng có thể thay đổi, vì nhiều luồng có thể đang cố gắng thay đổi trạng thái của cùng một đối tượng, dẫn đến một số luồng nhìn thấy trạng thái khác nhau của cùng một đối tượng, tùy thuộc vào thời gian đọc và ghi đối tượng đã nói vật.

Bằng cách có một đối tượng không thay đổi, người ta có thể đảm bảo rằng tất cả các luồng đang nhìn vào đối tượng sẽ thấy cùng một trạng thái, vì trạng thái của một đối tượng bất biến sẽ không thay đổi.


5
An toàn luồng (quan trọng đối với tính đồng thời) không phải là lợi thế duy nhất của tính bất biến; nó cũng có nghĩa là bạn không phải tạo các bản sao phòng thủ của các đối tượng và nó ngăn chặn lỗi vì bạn không thể sửa đổi nhầm các đối tượng không được sửa đổi.
Jesper

7
Thay vì trả lại bản sao của danh sách, bạn cũng có thể return Collections.unmodifiableList(list);trả lại chế độ xem chỉ đọc trên danh sách.
Jesper

18
Lớp học cũng phải được thực hiện final. Nếu không, nó có thể được mở rộng bằng phương thức setter (hoặc các loại phương thức thay đổi khác và trường có thể thay đổi).
Abhinav Sarkar

6
@AbhinavSarkar Không nhất thiết phải như vậy finalnếu lớp chỉ có privatecác trường vì chúng không thể được truy cập từ các lớp con.
icza

3
lớp @icza phải là cuối cùng, bởi vì chúng tôi có phương pháp getter công cộng, chúng ta có thể mở rộng các lớp và ghi đè các phương thức getter và sau đó thay đổi lĩnh vực này trong chúng tôi way..so nó không phải là bất biến nữa
Sunil

19

Ngoài các câu trả lời đã được đưa ra, tôi khuyên bạn nên đọc về tính bất biến trong Java hiệu quả, Phiên bản thứ 2, vì có một số chi tiết rất dễ bỏ sót (ví dụ: các bản sao phòng thủ). Thêm vào đó, phiên bản Java thứ hai hiệu quả. là phải đọc cho mọi nhà phát triển Java.


3
Đây là nguồn chính xác để xem xét. Những lợi ích được đề cập bao gồm từ mã ít bị lỗi hơn đến an toàn luồng.
gpampara

6

Bạn tạo một lớp không thay đổi như thế này:

public final class Immutable
{
    private final String name;

    public Immutable(String name) 
    {
        this.name = name;
    }

    public String getName() { return this.name; } 

    // No setter;
}

Sau đây là các yêu cầu để làm cho một lớp Java trở nên bất biến:

  • Lớp phải được khai báo là final(Vì vậy, không thể tạo các lớp con)
  • Các thành viên trong lớp phải được khai báo là final(Để chúng ta không thể thay đổi giá trị của nó sau khi tạo đối tượng)
  • Viết phương thức Getter cho tất cả các biến trong đó để nhận giá trị Thành viên
  • Không có phương pháp Setters

Các lớp bất biến rất hữu ích vì
- Chúng an toàn theo chuỗi.
- Họ cũng thể hiện điều gì đó sâu sắc về thiết kế của bạn: “Không thể thay đổi điều này.”, Khi nó được áp dụng, đó chính xác là thứ bạn cần.


5

Tính bất biến có thể đạt được chủ yếu bằng hai cách:

  • sử dụng finalthuộc tính phiên bản để tránh gán lại
  • sử dụng giao diện lớp đơn giản là không cho phép bất kỳ thao tác nào có thể sửa đổi những gì bên trong lớp của bạn (chỉ getters và không có setters

Ưu điểm của tính bất biến là các giả định mà bạn có thể thực hiện trên đối tượng này:

  • bạn đạt được quy tắc không có tác dụng phụ (điều đó thực sự phổ biến trên các ngôn ngữ lập trình hàm) và cho phép bạn sử dụng các đối tượng trong môi trường đồng thời dễ dàng hơn, vì bạn biết rằng chúng không thể thay đổi theo cách nguyên tử hoặc không nguyên tử khi chúng được sử dụng bởi nhiều chủ đề
  • triển khai các ngôn ngữ có thể xử lý các đối tượng này theo một cách khác, đặt chúng vào các vùng bộ nhớ được sử dụng cho dữ liệu tĩnh, cho phép sử dụng các đối tượng này nhanh hơn và an toàn hơn (đây là những gì xảy ra bên trong JVM cho các chuỗi)

Bất biến không giống như không có tác dụng phụ như nhau. Ví dụ, một đối tượng không thay đổi có thể tạo ra các tác dụng phụ như đăng nhập vào một tệp. Sẽ hơi không chính xác khi nói rằng việc làm cho một đối tượng bất biến cũng khiến nó không có tác dụng phụ.
Grundlefleck,

1
@Grundleflek, tôi nghĩ đây có thể là những sợi tóc bị chẻ đôi. Lớp không thể thay đổi nếu nó sửa đổi tệp nhật ký như một phần của hợp đồng và tệp nhật ký đó có thể truy cập được đối với các lớp khác. Nếu tệp nhật ký bị ẩn khỏi các lớp khác và không phải là một phần của hợp đồng của lớp thì lớp đó là bất biến một cách hiệu quả và thực sự không có tác dụng phụ đối với mọi ý định và mục đích. Phần giới thiệu (không có nguồn gốc) trên trang tác dụng phụ Wikipedia có nội dung "... một biểu thức được cho là có tác dụng phụ nếu ngoài việc tạo ra một giá trị, nó còn sửa đổi một số trạng thái hoặc có một tương tác có thể quan sát được với các hàm gọi."
Jeff Axelrod

3

Các lớp bất biến không thể gán lại giá trị sau khi nó được khởi tạo. Hàm khởi tạo gán giá trị cho các biến riêng của nó. Cho đến khi đối tượng trở thành null, các giá trị không thể thay đổi do không có sẵn các phương thức setter.

là bất biến nên đáp ứng sau,

  • Al các biến phải là riêng tư .
  • Không có phương pháp đột biến (bộ định tuyến) nào được cung cấp.
  • Tránh ghi đè phương thức bằng cách làm cho lớp cuối cùng (Tính bất biến mạnh) hoặc phương thức cuối cùng (Tính bất biến trong tuần).
  • Sao chép sâu nếu nó chứa các lớp không nguyên thủy hoặc có thể thay đổi.

/**
* Strong immutability - by making class final
*/
public final class TestImmutablity {

// make the variables private
private String Name;

//assign value when the object created
public TestImmutablity(String name) {
this.Name = name;
}

//provide getters to access values
public String getName() {

return this.Name;
}
}

Ưu điểm: Đối tượng bất biến chứa các giá trị khởi tạo của nó cho đến khi nó chết.

java-immutable-class-short-note


2

Lớp bất biến là những lớp có đối tượng không thể thay đổi sau khi tạo.

Các lớp bất biến rất hữu ích cho

  • Mục đích lưu vào bộ nhớ đệm
  • Môi trường đồng thời (ThreadSafe)
  • Khó thừa kế
  • Giá trị không thể thay đổi trong bất kỳ môi trường nào

Thí dụ

Lớp chuỗi

Ví dụ về mã

public final class Student {
    private final String name;
    private final String rollNumber;

    public Student(String name, String rollNumber) {
        this.name = name;
        this.rollNumber = rollNumber;
    }

    public String getName() {
        return this.name;
    }

    public String getRollNumber() {
        return this.rollNumber;
    }
}

2

Làm thế nào người ta có thể làm cho một lớp Java trở nên bất biến?

Từ JDK 14+ có JEP 359 , chúng ta có thể sử dụng " records". Đây là cách đơn giản nhất và hối hả để tạo lớp Immutable.

Một lớp bản ghi là một sóng mang trong suốt, không thay đổi nông đối với một tập hợp các trường cố định được gọi là bản ghi componentscung cấp statemô tả cho bản ghi. Mỗi thứ componentlàm phát sinh một finaltrường chứa giá trị đã cung cấp và một accessorphương thức để truy xuất giá trị. Tên trường và tên trình truy cập khớp với tên của thành phần.

Hãy xem xét ví dụ về việc tạo một hình chữ nhật bất biến

record Rectangle(double length, double width) {}

Không cần khai báo bất kỳ phương thức khởi tạo nào, không cần triển khai các phương thức equals & hashCode. Chỉ cần bất kỳ Hồ sơ nào cần có tên và mô tả trạng thái.

var rectangle = new Rectangle(7.1, 8.9);
System.out.print(rectangle.length()); // prints 7.1

Nếu bạn muốn xác thực giá trị trong quá trình tạo đối tượng, chúng ta phải khai báo hàm tạo một cách rõ ràng.

public Rectangle {

    if (length <= 0.0) {
      throw new IllegalArgumentException();
    }
  }

Phần thân của bản ghi có thể khai báo các phương thức tĩnh, trường tĩnh, bộ khởi tạo tĩnh, hàm tạo, phương thức thể hiện và các kiểu lồng nhau.

Phương pháp phiên bản

record Rectangle(double length, double width) {

  public double area() {
    return this.length * this.width;
  }
}

trường tĩnh, phương thức

Vì trạng thái phải là một phần của các thành phần, chúng tôi không thể thêm trường cá thể vào bản ghi. Tuy nhiên, chúng ta có thể thêm các trường và phương thức tĩnh:

record Rectangle(double length, double width) {

  static double aStaticField;

  static void aStaticMethod() {
    System.out.println("Hello Static");
  }
}

nhu cầu của tính bất biến là gì và có lợi thế nào khi sử dụng điều này?

Các câu trả lời đã đăng trước đây đủ tốt để biện minh cho sự cần thiết của tính bất biến và đó là ưu


0

Một cách khác để tạo đối tượng không thay đổi là sử dụng thư viện Immutables.org :

Giả sử rằng các phụ thuộc bắt buộc đã được thêm vào, hãy tạo một lớp trừu tượng với các phương thức truy cập trừu tượng. Bạn có thể làm tương tự bằng cách chú thích bằng giao diện hoặc thậm chí chú thích (@interface):

package info.sample;

import java.util.List;
import java.util.Set;
import org.immutables.value.Value;

@Value.Immutable
public abstract class FoobarValue {
  public abstract int foo();
  public abstract String bar();
  public abstract List<Integer> buz();
  public abstract Set<Long> crux();
}

Bây giờ có thể tạo và sau đó sử dụng triển khai không thay đổi đã tạo:

package info.sample;

import java.util.List;

public class FoobarValueMain {
  public static void main(String... args) {
    FoobarValue value = ImmutableFoobarValue.builder()
        .foo(2)
        .bar("Bar")
        .addBuz(1, 3, 4)
        .build(); // FoobarValue{foo=2, bar=Bar, buz=[1, 3, 4], crux={}}

    int foo = value.foo(); // 2

    List<Integer> buz = value.buz(); // ImmutableList.of(1, 3, 4)
  }
}

0

Một lớp bất biến chỉ đơn giản là một lớp mà các thể hiện của nó không thể sửa đổi được.

Tất cả thông tin chứa trong mỗi trường hợp được cố định trong suốt thời gian tồn tại của đối tượng, vì vậy không bao giờ có thể quan sát thấy thay đổi.

Các lớp bất biến dễ thiết kế, triển khai và sử dụng hơn các lớp có thể thay đổi.

Để làm cho một lớp trở nên bất biến, hãy làm theo năm quy tắc sau:

  1. Không cung cấp các phương thức sửa đổi trạng thái của đối tượng

  2. Đảm bảo rằng lớp học không thể được mở rộng.

  3. Làm cho tất cả các trường cuối cùng.

  4. Đặt tất cả các trường ở chế độ riêng tư.

  5. Đảm bảo quyền truy cập độc quyền vào bất kỳ thành phần có thể thay đổi nào.

Các đối tượng bất biến vốn dĩ an toàn theo luồng; chúng không yêu cầu đồng bộ hóa.

Các đối tượng bất biến có thể được chia sẻ tự do.

Các đối tượng bất biến tạo nên các khối xây dựng tuyệt vời cho các đối tượng khác


0

@Jack, Việc có các trường và bộ thiết lập cuối cùng trong lớp sẽ không làm cho lớp trở nên bất biến. từ khóa cuối cùng chỉ đảm bảo rằng một biến không bao giờ được gán lại. Bạn cần trả về bản sao sâu của tất cả các trường trong phương thức getter. Điều này sẽ đảm bảo rằng, sau khi lấy một đối tượng từ phương thức getter, trạng thái bên trong của đối tượng không bị xáo trộn.


-1

Là một người nói tiếng Anh không phải là bản ngữ, tôi không thích cách hiểu thông thường về "lớp không thay đổi" là "các đối tượng lớp được xây dựng là không thay đổi"; đúng hơn, bản thân tôi nghiêng về giải thích rằng "đối tượng lớp tự nó là bất biến".

Điều đó nói rằng, "lớp bất biến" là một loại đối tượng bất biến. Sự khác biệt là khi trả lời lợi ích là gì. Theo hiểu biết / diễn giải của tôi, lớp không thay đổi ngăn các đối tượng của nó khỏi các sửa đổi hành vi thời gian chạy.


-1

Hầu hết các câu trả lời ở đây là tốt, và một số đã đề cập đến các quy tắc nhưng tôi cảm thấy rất tốt khi đặt ra các từ tại sao và khi nào chúng ta cần tuân theo các quy tắc này. Vì vậy, tôi đưa ra lời giải thích bên dưới

  • Khai báo các biến thành viên là 'cuối cùng' - Khi chúng ta khai báo chúng là cuối cùng, trình biên dịch buộc chúng ta khởi tạo chúng. Chúng ta có thể khởi tạo trực tiếp, theo phương thức khởi tạo mặc định, bằng phương thức khởi tạo đối số. (Xem mã mẫu bên dưới) và sau khi khởi tạo, chúng ta không thể sửa đổi chúng vì chúng là cuối cùng.
  • Và tất nhiên nếu chúng ta cố gắng sử dụng Bộ cài đặt cho các biến cuối cùng đó, trình biên dịch sẽ ném ra lỗi.

    public class ImmutableClassExplored {
    
        public final int a; 
        public final int b;
    
        /* OR  
        Generally we declare all properties as private, but declaring them as public 
        will not cause any issues in our scenario if we make them final     
        public final int a = 109;
        public final int b = 189;
    
         */
        ImmutableClassExplored(){
            this. a = 111;
            this.b = 222;
        }
    
        ImmutableClassExplored(int a, int b){
            this.a = a;
            this.b= b;
        }
    }
    

Chúng ta có cần khai báo lớp là 'cuối cùng' không?

  • Không có từ khóa cuối cùng trong khai báo lớp, lớp có thể được kế thừa. Vì vậy, lớp con có thể ghi đè các phương thức getter. Ở đây chúng ta phải xem xét hai tình huống:

1. Chỉ có các thành viên nguyên thủy: Chúng ta không gặp vấn đề Nếu lớp chỉ có các thành viên nguyên thủy, thì chúng ta không cần khai báo lớp là cuối cùng.

2. Có đối tượng là biến thành viên: Nếu chúng ta có đối tượng là biến thành viên thì chúng ta phải làm cho các thành viên của các đối tượng đó cũng là cuối cùng. Có nghĩa là chúng ta cần phải đi sâu xuống dưới cái cây và làm cho tất cả các đối tượng / nguyên thủy là cuối cùng mà có thể không phải lúc nào cũng có thể thực hiện được. Vì vậy, giải pháp là tạo lớp cuối cùng để ngăn chặn sự kế thừa. Vì vậy, không có câu hỏi về các phương thức getter ghi đè lớp con.


-1

Chú thích @Value của Lombok có thể được sử dụng để tạo các lớp bất biến. Nó đơn giản như mã bên dưới.

@Value
public class LombokImmutable {
    int id;
    String name;
}

Theo tài liệu trên trang của Lombok:

@Value là biến thể bất biến của @Data; tất cả các trường được đặt ở chế độ riêng tư và cuối cùng theo mặc định và bộ định tuyến không được tạo. Bản thân lớp cũng được đặt cuối cùng theo mặc định, bởi vì tính bất biến không phải là thứ có thể bị buộc vào một lớp con. Giống như @Data, các phương thức toString (), equals () và hashCode () hữu ích cũng được tạo, mỗi trường nhận được một phương thức getter và một hàm tạo bao hàm mọi đối số (ngoại trừ các trường cuối cùng được khởi tạo trong khai báo trường). .

Một ví dụ hoạt động đầy đủ có thể được tìm thấy ở đây.


Trước khi trả lời một câu hỏi cũ, có một câu trả lời được chấp nhận (tìm màu xanh lá cây ✓) cũng như các câu trả lời khác, hãy đảm bảo câu trả lời của bạn thêm điều gì đó mới hoặc hữu ích liên quan đến chúng. Hãy cẩn thận khi trả lời câu hỏi của OP Làm thế nào để tạo ra một lớp Java là bất biến, nhu cầu của tính bất biến là gì và có lợi thế nào khi sử dụng điều này không? . - Bạn chỉ đưa ra câu trả lời một phần mà chỉ nói rằng người ta có thể sử dụng Lombok, một khuôn khổ / thư viện của bên thứ ba, không nhất thiết phải là câu hỏi của OP. Ngoài ra Java 15 đã ra đời, tại sao lại sử dụng Lombok khi Java recordcó thể được sử dụng?
Ivo Mori

Tôi đang đưa ra một trong những lựa chọn về cách đạt được điều này. Không phải ai cũng sử dụng Java 15, phần lớn các ứng dụng ngày nay đều chạy trên các phiên bản trước, vì vậy bạn nói tại sao sử dụng Lombok cũng không có nhiều ý nghĩa. Trong dự án hiện tại của tôi, chúng tôi gần đây đã chuyển sang Java 11 và đang sử dụng Lombok để đạt được các lớp bất biến. Ngoài ra, câu trả lời của tôi là một bổ sung cho những câu trả lời đã có. Điều gì là bất biến đã được trả lời, tôi đoán bạn đang mong đợi tôi viết lại điều đó để hoàn thành.
Anubhav

Đủ công bằng. Tôi không ủng hộ cũng không ủng hộ. Tôi đã cố gắng chỉ ra lý do tại sao hai người khác lại bỏ phiếu cho câu trả lời này. Tùy thuộc vào bạn nếu và cách bạn muốn chỉnh sửa câu trả lời của mình.
Ivo Mori
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.