Bộ sưu tập bất biến vs không thể thay đổi


170

Từ Tổng quan về Khung sưu tập :

Các bộ sưu tập không hỗ trợ các hoạt động sửa đổi (như add, removeclear) được gọi là không thể thay đổi . Bộ sưu tập không thể thay đổi được có thể sửa đổi .

Các bộ sưu tập đảm bảo bổ sung rằng không có thay đổi nào trong Collectionđối tượng sẽ được nhìn thấy được gọi là bất biến . Bộ sưu tập không phải là bất biến là có thể thay đổi .

Tôi không thể hiểu sự khác biệt.
Sự khác biệt giữa không thể thay đổibất biến ở đây là gì?

Câu trả lời:


207

Một bộ sưu tập không thể thay đổi thường là một trình bao bọc xung quanh một bộ sưu tập có thể sửa đổi mà mã khác có thể vẫn có quyền truy cập . Vì vậy, trong khi bạn không thể thực hiện bất kỳ thay đổi nào nếu bạn chỉ có một tham chiếu đến bộ sưu tập không thể thay đổi, bạn không thể dựa vào nội dung không thay đổi.

Một bộ sưu tập bất biến đảm bảo rằng không có gì có thể thay đổi bộ sưu tập nữa. Nếu nó bao bọc một bộ sưu tập có thể sửa đổi, nó đảm bảo rằng không có mã nào khác có quyền truy cập vào bộ sưu tập có thể sửa đổi đó. Lưu ý rằng mặc dù không có mã nào có thể thay đổi đối tượng mà bộ sưu tập chứa tham chiếu đến, nhưng chính các đối tượng đó vẫn có thể thay đổi - tạo ra một bộ sưu tập bất biếnStringBuilder không bằng cách nào đó "đóng băng" các đối tượng đó.

Về cơ bản, sự khác biệt là về việc liệu các mã khác có thể thay đổi bộ sưu tập phía sau lưng của bạn hay không.


63
Một bộ sưu tập bất biến không đảm bảo rằng không có gì có thể thay đổi nữa. Nó chỉ đảm bảo rằng bộ sưu tập không thể được thay đổi (và không phải bằng cách gói, mà bằng cách sao chép). Các đối tượng có mặt trong bộ sưu tập vẫn có thể được thay đổi và không có sự đảm bảo nào được đưa ra cho những đối tượng đó.
Nomery Nomus

8
@HieryNomus: Lưu ý rằng tôi đã không nói rằng không có gì có thể thay đổi - Tôi đã nói không có gì có thể thay đổi bộ sưu tập.
Jon Skeet

1
ok, có thể đã đọc sai điều đó;) Nhưng thật tốt khi làm rõ điều đó.
Nomery Nomus

5
Vì vậy, những gì bạn đang nói. Là cho sự bất biến thực sự, bạn cần một bộ sưu tập bất biến có chứa các loại vật phẩm bất biến.
Evan Plaice

1
@savanibharat: điều đó phụ thuộc vào việc có bất kỳ đường dẫn mã nào vẫn có thể sửa đổi hay không list. Nếu một cái gì đó sau này có thể gọi list.add(10)thì collsẽ phản ánh sự thay đổi đó, vì vậy không, tôi sẽ không gọi nó là bất biến.
Jon Skeet

92

Về cơ bản unModifiable Bộ sưu tập là một khung nhìn, do đó, gián tiếp nó vẫn có thể được 'sửa đổi' từ một số tham chiếu khác có thể sửa đổi. Ngoài ra, đây chỉ là chế độ xem chỉ đọc của bộ sưu tập ann ann, Khi bộ sưu tập nguồn thay đổi Bộ sưu tập không thể thay đổi sẽ luôn xuất hiện với các giá trị mới nhất.

Tuy nhiên, immutableBộ sưu tập có thể được coi là một bản sao chỉ đọc của bộ sưu tập khác và không thể sửa đổi. Trong trường hợp này khi bộ sưu tập nguồn thay đổi, Bộ sưu tập không thay đổi không phản ánh các thay đổi

Dưới đây là một thử nghiệm để hình dung sự khác biệt này.

@Test
public void testList() {

    List<String> modifiableList = new ArrayList<String>();
    modifiableList.add("a");

    System.out.println("modifiableList:"+modifiableList);
    System.out.println("--");


    //unModifiableList

    assertEquals(1, modifiableList.size());

    List<String> unModifiableList=Collections.unmodifiableList(
                                        modifiableList);

    modifiableList.add("b");

    boolean exceptionThrown=false;
    try {
        unModifiableList.add("b");
        fail("add supported for unModifiableList!!");
    } catch (UnsupportedOperationException e) {
        exceptionThrown=true;
        System.out.println("unModifiableList.add() not supported");
    }
    assertTrue(exceptionThrown);

    System.out.println("modifiableList:"+modifiableList);
    System.out.println("unModifiableList:"+unModifiableList);

    assertEquals(2, modifiableList.size());
    assertEquals(2, unModifiableList.size());
            System.out.println("--");



            //immutableList


    List<String> immutableList=Collections.unmodifiableList(
                            new ArrayList<String>(modifiableList));

    modifiableList.add("c");

    exceptionThrown=false;
    try {
        immutableList.add("c");
        fail("add supported for immutableList!!");
    } catch (UnsupportedOperationException e) {
        exceptionThrown=true;
        System.out.println("immutableList.add() not supported");
    }
    assertTrue(exceptionThrown);


    System.out.println("modifiableList:"+modifiableList);
    System.out.println("unModifiableList:"+unModifiableList);
    System.out.println("immutableList:"+immutableList);
    System.out.println("--");

    assertEquals(3, modifiableList.size());
    assertEquals(3, unModifiableList.size());
    assertEquals(2, immutableList.size());

}

Đầu ra

modifiableList:[a]
--
unModifiableList.add() not supported
modifiableList:[a, b]
unModifiableList:[a, b]
--
immutableList.add() not supported
modifiableList:[a, b, c]
unModifiableList:[a, b, c]
immutableList:[a, b]
--

Tôi không thể thấy bất kỳ sự khác biệt nào, bạn có thể vui lòng chỉ ra sự bất biến là khác nhau như thế nào không? Tôi có thể thấy cả bất biến và không thể thay đổi đang ném lỗi và thêm không được hỗ trợ. Am i thiếu cái gì ở đây?
AKS

2
@AKS Vui lòng xem đầu ra của ba mục nhập danh sách cuối cùng sau khi thêm 'c' vào danh sách trong khi cả kích thước modifiableListvà kích thước unModifiableListtăng immutableListkhông thay đổi
Prashant Bhate

1
Oh! hiểu rồi! :) .. Vì vậy, ở đây bạn đã sửa đổi unmodifableList bằng cách sử dụng các thay đổi trong modifiableList, nhưng ImmutableList không thể được sửa đổi. Nhưng cũng giống như cách bạn có thể sửa đổi ImmutableList, tôi nghĩ rằng ở đây khách hàng sẽ chỉ có quyền truy cập vào tham chiếu ImmutableList, tham chiếu đến modifiableList, sử dụng mà ImmutableList được tạo, sẽ không được hiển thị cho khách hàng. đúng?
AKS

1
có bởi vì không có tài liệu tham khảo nào về danh sách new ArrayList<String>(modifiableList)bất biến không thể sửa đổi
Prashant Bhate

@PrashantBhate Xin chào, không có tài liệu tham khảo new ArrayList<String>(modifiableList)new? Cảm ơn.
Unheilig

11

Tôi nghĩ rằng sự khác biệt chính là chủ sở hữu của bộ sưu tập có thể thay đổi có thể muốn cung cấp quyền truy cập vào bộ sưu tập cho một số mã khác, nhưng cung cấp quyền truy cập đó thông qua giao diện không cho phép mã khác sửa đổi bộ sưu tập (trong khi bảo lưu khả năng đó đến mã sở hữu). Vì vậy, bộ sưu tập không phải là bất biến, nhưng một số người dùng nhất định không được phép thay đổi bộ sưu tập.

Hướng dẫn Java Collection Wrapper của Oracle có điều này để nói (nhấn mạnh thêm):

Các hàm bao không thể thay đổi có hai cách sử dụng chính như sau:

  • Để làm cho một bộ sưu tập bất biến một khi nó đã được xây dựng. Trong trường hợp này, thực tế tốt là không duy trì tham chiếu đến bộ sưu tập sao lưu. Điều này hoàn toàn đảm bảo tính bất biến.
  • Để cho phép một số khách hàng nhất định truy cập chỉ đọc vào cấu trúc dữ liệu của bạn. Bạn giữ một tham chiếu đến bộ sưu tập sao lưu nhưng đưa ra một tham chiếu đến trình bao bọc. Bằng cách này, khách hàng có thể nhìn nhưng không sửa đổi, trong khi bạn duy trì quyền truy cập đầy đủ .

3

Nếu chúng ta đang nói về JDK Unmodifiable*vs ổi Immutable*, thực sự sự khác biệt cũng nằm ở hiệu năng . Các bộ sưu tập không thay đổi có thể vừa nhanh hơn và tiết kiệm bộ nhớ hơn nếu chúng không phải là các hàm bao quanh các bộ sưu tập thông thường (các triển khai JDK là các hàm bao). Trích dẫn đội ổi :

JDK cung cấp các phương thức Collections.unmodifiableXXX, nhưng theo chúng tôi, chúng có thể là các phương thức

<...>

  • không hiệu quả: các cấu trúc dữ liệu vẫn có tất cả chi phí hoạt động của các bộ sưu tập có thể thay đổi, bao gồm kiểm tra sửa đổi đồng thời, thêm không gian trong bảng băm, v.v.

Nghĩ đến hiệu suất, bạn cũng nên tính đến việc một trình bao bọc không thể thay đổi không sao chép bộ sưu tập, trong đó phiên bản bất biến được sử dụng trong ổi và bây giờ cũng trong jdk9 + với ví dụ List.of(...)thực sự sao chép hai lần!
benez

2

Để trích dẫn Hướng dẫn Java ™ :

Không giống như các trình bao bọc đồng bộ hóa, bổ sung chức năng cho bộ sưu tập được bao bọc, các trình bao bọc không thể thay đổi sẽ mất chức năng. Cụ thể, họ lấy đi khả năng sửa đổi bộ sưu tập bằng cách chặn tất cả các hoạt động sẽ sửa đổi bộ sưu tập và ném UnceptionedOperationException . Các hàm bao không thể thay đổi có hai cách sử dụng chính như sau:

  • Để làm cho một bộ sưu tập bất biến một khi nó đã được xây dựng. Trong trường hợp này, thực tế tốt là không duy trì tham chiếu đến bộ sưu tập sao lưu. Điều này hoàn toàn đảm bảo tính bất biến.

  • Để cho phép một số khách hàng nhất định truy cập chỉ đọc vào cấu trúc dữ liệu của bạn. Bạn giữ một tham chiếu đến bộ sưu tập sao lưu nhưng đưa ra một tham chiếu đến trình bao bọc. Bằng cách này, khách hàng có thể nhìn nhưng không sửa đổi, trong khi bạn duy trì quyền truy cập đầy đủ.

(nhấn mạnh của tôi)

Điều này thực sự tổng hợp nó lên.


1

Như đã lưu ý ở trên không thể thay đổi không giống như bất biến bởi vì một bộ sưu tập không thể thay đổi có thể bị thay đổi nếu ví dụ một bộ sưu tập không thể thay đổi có một bộ sưu tập ủy nhiệm cơ bản được tham chiếu bởi một số đối tượng khác và đối tượng đó thay đổi nó.

Về bất biến, nó thậm chí không được xác định rõ. Tuy nhiên, nói chung, điều đó có nghĩa là đối tượng "sẽ không thay đổi", nhưng điều đó sẽ cần được xác định theo cách đệ quy. Ví dụ, tôi có thể định nghĩa bất biến trên các lớp có các biến đối tượng là tất cả các nguyên hàm và các phương thức của chúng đều không chứa đối số và trả về các nguyên hàm. Các phương thức sau đó đệ quy cho phép các biến đối tượng là bất biến và tất cả các phương thức để chứa các đối số là bất biến và trả về các giá trị bất biến. Các phương thức nên được đảm bảo để trả về cùng một giá trị theo thời gian.

Giả sử rằng chúng ta có thể làm điều đó, đó cũng là chủ đề khái niệm an toàn. Và bạn có thể được dẫn dắt để tin rằng bất biến (hoặc không thay đổi theo thời gian) cũng ngụ ý chủ đề an toàn. Tuy nhiên, đó không phải là trường hợpvà đó là điểm chính tôi đang làm ở đây chưa được ghi nhận trong các câu trả lời khác. Tôi có thể xây dựng một đối tượng bất biến luôn trả về cùng kết quả nhưng không an toàn cho chuỗi. Để thấy điều này giả sử rằng tôi xây dựng một bộ sưu tập bất biến bằng cách duy trì các bổ sung và xóa theo thời gian. Bây giờ bộ sưu tập bất biến trả về các phần tử của nó bằng cách xem bộ sưu tập nội bộ (có thể thay đổi theo thời gian) và sau đó (bên trong) thêm và xóa các phần tử đã được thêm hoặc xóa sau khi tạo bộ sưu tập. Rõ ràng, mặc dù bộ sưu tập sẽ luôn trả về các phần tử giống nhau, nhưng nó không phải là luồng an toàn chỉ vì nó sẽ không bao giờ thay đổi giá trị.

Bây giờ chúng ta có thể định nghĩa bất biến là các đối tượng là luồng an toàn và sẽ không bao giờ thay đổi. Tuy nhiên, có các hướng dẫn để tạo các lớp bất biến thường dẫn đến các lớp như vậy, tuy nhiên, hãy nhớ rằng có thể có các cách để tạo các lớp bất biến, đòi hỏi phải chú ý đến an toàn luồng, ví dụ, như được mô tả trong ví dụ về bộ sưu tập "ảnh chụp nhanh" ở trên.


1

Các hướng dẫn Java ™ nói như sau:

Không giống như các trình bao bọc đồng bộ hóa, bổ sung chức năng cho bộ sưu tập được bao bọc, các trình bao bọc không thể thay đổi sẽ mất chức năng. Cụ thể, họ lấy đi khả năng sửa đổi bộ sưu tập bằng cách chặn tất cả các hoạt động sẽ sửa đổi bộ sưu tập và ném UnceptionedOperationException. Các hàm bao không thể thay đổi có hai cách sử dụng chính như sau:

Để làm cho một bộ sưu tập bất biến một khi nó đã được xây dựng. Trong trường hợp này, thực tế tốt là không duy trì tham chiếu đến bộ sưu tập sao lưu. Điều này hoàn toàn đảm bảo tính bất biến.

Để cho phép một số khách hàng nhất định truy cập chỉ đọc vào cấu trúc dữ liệu của bạn. Bạn giữ một tham chiếu đến bộ sưu tập sao lưu nhưng đưa ra một tham chiếu đến trình bao bọc. Bằng cách này, khách hàng có thể nhìn nhưng không sửa đổi, trong khi bạn duy trì quyền truy cập đầy đủ.

Tôi nghĩ rằng đó là một lời giải thích đủ tốt để hiểu sự khác biệt.

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.