Sự khác biệt giữa List.of và Arrays.asList là gì?


117

Java 9 giới thiệu phương pháp nhà máy mới cho các danh sách, List.of:

List<String> strings = List.of("first", "second");

Sự khác biệt giữa tùy chọn trước đó và tùy chọn mới là gì? Đó là, sự khác biệt giữa điều này là gì:

Arrays.asList(1, 2, 3);

và điều này:

List.of(1, 2, 3);

1
Xem thêm bài nói chuyện này của Stuart "Beaker" Marks.
user1803551

20
@ user1803551 Mặc dù tôi hiểu sự thất vọng của bạn, nhưng lý do này có thể đặt ra một tiền lệ thực sự không mong muốn. Rất nhiều câu hỏi ở đây có câu trả lời được 'nêu rõ ràng' (tùy thuộc vào cách người ta định nghĩa điều này). Tôi muốn kêu gọi bạn để mang lại cuộc thảo luận này để meta nhưng tôi là một cuộc thảo luận khá chắc chắn như vậy nên đã tồn tại (và tôi hy vọng ai đó có thể tìm thấy nó và liên kết nó :-)
Dimitris Fasarakis Hilliard

4
@ user1803551 Javadocs không đề cập đến sự khác biệt giữa chi tiết triển khai của hai phương pháp này (như tiêu thụ không gian hoặc hiệu suất). Tôi nghĩ mọi người cũng muốn biết những chi tiết này.
ZhekaKozlov

5
@ZhekaKozlov Câu trả lời được chấp nhận và siêu ủng hộ cũng không. Điều đó cho bạn biết gì về các tiêu chuẩn được chấp nhận? Nó thậm chí có ít thông tin hơn so với trong tài liệu (tuần tự hóa, danh tính, thứ tự). Nếu có bất cứ điều gì, hãy gửi yêu cầu tới OpenJDK để thêm thông tin đó.
user1803551

3
Câu hỏi này đang được thảo luận trên meta .
Dimitris Fasarakis Hilliard

Câu trả lời:


173

Arrays.asListtrả về một danh sách có thể thay đổi trong khi danh sách được trả về List.ofbất biến :

List<Integer> list = Arrays.asList(1, 2, null);
list.set(1, 10); // OK

List<Integer> list = List.of(1, 2, 3);
list.set(1, 10); // Fails with UnsupportedOperationException

Arrays.asListcho phép các phần tử null trong khi List.ofkhông:

List<Integer> list = Arrays.asList(1, 2, null); // OK
List<Integer> list = List.of(1, 2, null); // Fails with NullPointerException

contains cư xử khác với nulls:

List<Integer> list = Arrays.asList(1, 2, 3);
list.contains(null); // Returns false

List<Integer> list = List.of(1, 2, 3);
list.contains(null); // Fails with NullPointerException

Arrays.asListtrả về một dạng xem của mảng đã truyền, vì vậy các thay đổi đối với mảng cũng sẽ được phản ánh trong danh sách. Vì List.ofđiều này không đúng:

Integer[] array = {1,2,3};
List<Integer> list = Arrays.asList(array);
array[1] = 10;
System.out.println(list); // Prints [1, 10, 3]

Integer[] array = {1,2,3};
List<Integer> list = List.of(array);
array[1] = 10;
System.out.println(list); // Prints [1, 2, 3]

22
Đối với một danh sách hoạt động khác nhau dựa trên cách nó được xây dựng dường như không hướng đối tượng cho lắm đối với tôi. Có lẽ nếu List.of trả về kiểu ImmutableList, điều này sẽ có ý nghĩa. Đây là một sự trừu tượng rất rò rỉ ở đây.
Sandy Chapman

5
Tôi không phải là nhà phát triển Java, vì vậy hãy coi đó là một quan sát bình thường. Có thể có một lý do chính đáng để hành vi khác nhau, nhưng nếu tôi có một phương thức trả về Danh sách <Integer> như ví dụ, giao diện sẽ không đủ để tôi biết liệu tôi có nhận được ngoại lệ thời gian chạy hay không nếu tôi kiểm tra nó cho null. Tương tự như vậy, một thay đổi trong việc triển khai các phương pháp đó có thể ảnh hưởng đến mã ở xa trang web gọi của phương pháp của tôi nếu việc kiểm tra đó xảy ra ở nơi khác. @Nicolai
Sandy Chapman

8
@SandyChapman đây có thể là hành vi không mong muốn đối với một số (hoặc hầu hết?), Nhưng đó là hành vi được ghi lại. Từ List.contains(Object o)javadoc của : "Throws [...] NullPointerException - nếu phần tử được chỉ định là null và danh sách này không cho phép phần tử null (tùy chọn)". Hoặc từ phần giới thiệu dài dòng của giao diện mà ít người đọc: "Một số triển khai bộ sưu tập có hạn chế đối với các phần tử mà chúng có thể chứa"
Aaron

11
@Aaron cũng ít nhất đó là một tài liệu tốt rò rỉ trừu tượng :)
Sandy Chapman

6
@Sandy Chapman: List.of không trả về một số ImmutableListkiểu, tên thực của nó chỉ là một chi tiết triển khai không công khai. Nếu nó được công khai và ai đó đã truyền nó Listlại, thì sự khác biệt là ở đâu? Đâu là sự khác biệt đối với Arrays.asList, trả về một Listtriển khai không công khai, tạo ra một ngoại lệ khi cố gắng addhoặc remove, hoặc danh sách được trả về Collections.unmodifiableListkhông cho phép sửa đổi gì cả? Đó là tất cả về các hợp đồng được chỉ định trong Listgiao diện. Các giao diện bộ sưu tập với các phương pháp tùy chọn luôn luôn là bất tịnh OOP kể từ Java 1.2 ...
Holger

31

Sự khác biệt giữa Arrays.asListList.of

Xem JavaDocs và bài nói chuyện này của Stuart Marks (hoặc các phiên bản trước của nó).

Tôi sẽ sử dụng phần sau cho các ví dụ mã:

List<Integer> listOf = List.of(...);
List<Integer> asList = Arrays.asList(...);
List<Integer> unmodif = Collections.unmodifiableList(asList);

Tính bất biến của cấu trúc (Hoặc: tính không thay đổi)

Bất kỳ nỗ lực nào để thay đổi cấu trúcList.of sẽ dẫn đến UnsupportedOperationException. Điều đó bao gồm các hoạt động như thêm , đặtloại bỏ . Tuy nhiên, bạn có thể thay đổi nội dung của các đối tượng trong danh sách (nếu các đối tượng không phải là bất biến), do đó, danh sách không phải là "hoàn toàn bất biến".

Đây cũng là số phận đối với danh sách không thể sửa đổi được tạo bằng Collections.unmodifiableList. Chỉ có danh sách này là chế độ xem của danh sách gốc, vì vậy nó có thể thay đổi nếu bạn thay đổi danh sách ban đầu.

Arrays.asListkhông phải là bất biến hoàn toàn, nó không có hạn chế set.

listOf.set(1, "a");  // UnsupportedOperationException
unmodif.set(1, "a"); // UnsupportedOperationException
asList.set(1, "a");  // modified unmodif! unmodif is not truly unmodifiable

Tương tự, thay đổi mảng ủng hộ (nếu bạn giữ nó) sẽ thay đổi danh sách.

Tính bất biến của cấu trúc đi kèm với nhiều tác dụng phụ liên quan đến mã hóa phòng thủ, đồng thời và bảo mật nằm ngoài phạm vi của câu trả lời này.

Thù địch vô nghĩa

List.ofvà bất kỳ bộ sưu tập nào kể từ Java 1.5 không cho phép nulldưới dạng một phần tử. Cố gắng vượt qua nulldưới dạng một phần tử hoặc thậm chí là tra cứu sẽ dẫn đến một NullPointerException.

Arrays.asListlà một tập hợp từ 1.2 (Khung Bộ sưu tập), nó cho phép nulls.

listOf.contains(null);  // NullPointerException
unmodif.contains(null); // allowed
asList.contains(null);  // allowed

Biểu mẫu được tuần tự hóa

Kể từ khi List.ofđược giới thiệu trong Java 9 và các danh sách được tạo bằng phương pháp này có dạng tuần tự hóa (nhị phân) của riêng chúng, chúng không thể được giải mã trên các phiên bản JDK trước đó (không có khả năng tương thích nhị phân ). Tuy nhiên, bạn có thể hủy / tuần tự hóa bằng JSON, chẳng hạn.

Danh tính

Arrays.asListcuộc gọi nội bộ new ArrayList, đảm bảo bất bình đẳng tham chiếu.

List.ofphụ thuộc vào việc thực hiện nội bộ. Các trường hợp trả về có thể có tham chiếu bình đẳng, nhưng vì điều này không được đảm bảo nên bạn không thể dựa vào nó.

asList1 == asList2; // false
listOf1 == listOf2; // true or false

Đáng nói là danh sách là bằng nhau (thông qua List.equals) nếu chúng chứa các phần tử giống nhau theo cùng một thứ tự, bất kể chúng được tạo ra như thế nào hoặc chúng hỗ trợ hoạt động nào.

asList.equals(listOf); // true i.f.f. same elements in same order

Triển khai (cảnh báo: chi tiết có thể thay đổi qua các phiên bản)

Nếu số phần tử trong danh sách List.oflà 2 hoặc ít hơn, các phần tử được lưu trữ trong các trường của một lớp chuyên biệt (nội bộ). Một ví dụ là danh sách lưu trữ 2 phần tử (một phần nguồn):

static final class List2<E> extends AbstractImmutableList<E> {
    private final E e0;
    private final E e1;

    List2(E e0, E e1) {
        this.e0 = Objects.requireNonNull(e0);
        this.e1 = Objects.requireNonNull(e1);
    }
}

Nếu không, chúng được lưu trữ trong một mảng theo kiểu tương tự như Arrays.asList.

Hiệu quả về thời gian và không gian

Các List.oftriển khai dựa trên trường (kích thước <2) thực hiện nhanh hơn một chút đối với một số hoạt động. Ví dụ: size()có thể trả về một hằng số mà không cần tìm nạp độ dài mảng và contains(E e)không yêu cầu chi phí lặp lại.

Việc tạo danh sách không thể sửa đổi thông qua List.ofcũng nhanh hơn. So sánh hàm tạo ở trên với 2 phép gán tham chiếu (và thậm chí là phép gán cho số lượng phần tử tùy ý) để

Collections.unmodifiableList(Arrays.asList(...));

tạo 2 danh sách cộng với chi phí khác. Về không gian, bạn tiết kiệm được UnmodifiableListtrình bao bọc cộng với một số xu. Cuối cùng, số tiền tiết kiệm HashSettương đương được thuyết phục hơn.


Thời gian kết luận: sử dụng List.ofkhi bạn muốn một danh sách không thay đổi và Arrays.asListkhi bạn muốn một danh sách có thể thay đổi (như hình trên).


1
Đối với những người tự hỏi tại sao câu trả lời này tồn tại, hãy xem phần này .
user1803551

3
Arrays.asListkhông hoàn toàn có thể biến đổi. asList.add(1);ném một UnsupportedOperationException.
mapeters

"Không thù địch" là một cách tuyệt vời để đặt nó. Tôi hầu như không thể sử dụng List.ofbất cứ lúc nào mọi người có thể muốn gọi containsvà không ngạc nhiên bởi NullPointerException.
Noumenon

14

Hãy tóm tắt sự khác biệt giữa List.ofArrays.asList

  1. List.ofcó thể được sử dụng tốt nhất khi tập dữ liệu ít hơn và không thay đổi, trong khi Arrays.asListcó thể được sử dụng tốt nhất trong trường hợp tập dữ liệu lớn và động.

  2. List.ofchiếm rất ít không gian trên không vì nó có triển khai dựa trên trường và tiêu thụ ít không gian đống hơn, cả về chi phí cố định và trên cơ sở từng phần tử. trong khi Arrays.asListchiếm nhiều không gian hơn vì trong khi khởi tạo, nó tạo ra nhiều đối tượng hơn trong heap.

  3. Bộ sưu tập được trả về List.oflà không thay đổi và do đó an toàn theo chuỗi trong khi Bộ sưu tập được trả về Arrays.asListcó thể thay đổi và không an toàn cho chuỗi. (Các trường hợp tập hợp bất biến thường tiêu tốn ít bộ nhớ hơn nhiều so với các bản sao có thể thay đổi của chúng.)

  4. List.ofkhông cho phép phần tử null trong khi Arrays.asListcho phép phần tử null .


2
"Các thể hiện bộ sưu tập bất biến thường tiêu tốn ít bộ nhớ hơn nhiều so với các đối tác có thể thay đổi của chúng." - Có thật không? Bạn có muốn giải thích thêm một chút về điều đó không - ý bạn là vì chúng có thể được chia sẻ một cách an toàn, hay bạn có nghĩa là các phiên bản của chính chúng ta có thể được triển khai hiệu quả hơn bằng cách nào đó?
Hulk

1
@Hulk Người trả lời đúng về hiệu quả sử dụng không gian. Xem bài nói chuyện của Stuart Marks: youtu.be/q6zF3vf114M?t=49m48s
ZhekaKozlov

2
@ZhekaKozlov Điều đó có vẻ đúng nói chung, nhưng tôi rất nghi ngờ rằng nó đúng khi nói về Arrays.asListso với List.of, vì điều đó thực sự chỉ là một lớp bao bọc xung quanh một mảng. Ít nhất thì việc triển khai OpenJDK dường như có chi phí cực kỳ nhỏ. Trên thực tế, List.ofsẽ cần phải tạo bản sao của bất kỳ mảng nào được truyền vào, vì vậy trừ khi bản thân mảng đó sẽ sớm được GC, nếu không thì nó sẽ có vẻ như List.ofcó bộ nhớ lớn hơn đáng kể.
Chris Hayes

4
@ChrisHayes Ít nhất List.of(x)List.of(x, y)hiệu quả hơn bởi vì họ không bố trí mảng ở tất cả
ZhekaKozlov

2
@Hulk: đừng quên rằng các List.ofphương thức này không bắt buộc phải trả về danh sách mới mỗi lần. Các danh sách này có danh tính không xác định, vì vậy có thể có bộ nhớ đệm hoặc sao chép hoặc phân cấp độ rộng được xử lý ở cấp JVM. Nếu không có trong phiên bản này, thì có lẽ trong phiên bản tiếp theo. Nó được hợp đồng cho phép. Ngược lại, Array.asListphụ thuộc vào danh tính của mảng mà bạn đang chuyển vào, vì danh sách kết quả là dạng xem có thể thay đổi trên mảng, phản ánh tất cả thay đổi theo hai chiều.
Holger

2

Ngoài các câu trả lời trên, có một số hoạt động nhất định mà cả hai List::ofArrays::asListkhác nhau:

+----------------------+---------------+----------+----------------+---------------------+
|      Operations      | SINGLETONLIST | LIST::OF | ARRAYS::ASLIST | JAVA.UTIL.ARRAYLIST |
+----------------------+---------------+----------+----------------+---------------------+
|          add         |             |       |              |          ✔️          |
+----------------------+---------------+----------+----------------+---------------------+
|        addAll        |             |       |              |          ✔️          |
+----------------------+---------------+----------+----------------+---------------------+
|         clear        |             |       |              |          ✔️          |
+----------------------+---------------+----------+----------------+---------------------+
|        remove        |             |       |              |          ✔️          |
+----------------------+---------------+----------+----------------+---------------------+
|       removeAll      |       ❗️       |        |        ❗️       |          ✔️          |
+----------------------+---------------+----------+----------------+---------------------+
|       retainAll      |       ❗️       |       |        ❗️        |          ✔️          |
+----------------------+---------------+----------+----------------+---------------------+
|      replaceAll      |             |       |        ✔️       |          ✔️          |
+----------------------+---------------+----------+----------------+---------------------+
|          set         |             |       |        ✔️       |          ✔️          |
+----------------------+---------------+----------+----------------+---------------------+
|         sort         |       ✔️       |        |        ✔️      |          ✔️          |
+----------------------+---------------+----------+----------------+---------------------+
|  remove on iterator  |             |       |              |          ✔️          |
+----------------------+---------------+----------+----------------+---------------------+
| set on list-iterator |             |       |        ✔️       |          ✔️          |
+----------------------+---------------+----------+----------------+---------------------+
  1. ✔️ có nghĩa là phương pháp được hỗ trợ
  2. ❌ có nghĩa là việc gọi phương thức này sẽ tạo ra một ngoại lệ không được hỗ trợ
  3. ❗️ có nghĩa là phương thức chỉ được hỗ trợ nếu các đối số của phương thức không gây ra đột biến, ví dụ: Collections.singletonList ("foo"). KeepAll ("foo") thì được nhưng Collections.singletonList ("foo"). KeepAll ("bar" ) ném một ngoại lệ không được hỗ trợ

Thông tin thêm về Bộ sưu tập :: singletonList Vs. Danh sách của


1
câu trả lời cho kỳ thi java
povis
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.