Jaka jest różnica między List.of i Arrays.asList?

117

Java 9 wprowadziła nowe metody fabryczne dla list List.of:

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

Jaka jest różnica między poprzednią a nową opcją? To znaczy, jaka jest różnica między tym:

Arrays.asList(1, 2, 3);

i to:

List.of(1, 2, 3);
ZhekaKozlov
źródło
1
Zobacz także wykład Stuarta „Beaker” Marksa.
user1803551
20
@ user1803551 Chociaż rozumiem twoją frustrację, to rozumowanie może stanowić naprawdę niechciany precedens. Wiele pytań tutaj ma odpowiedź, która jest „jasno określona” (w zależności od tego, jak się to zdefiniuje). Zachęcam cię do przeniesienia tej dyskusji do meta, ale jestem prawie pewien, że taka dyskusja powinna już istnieć (i mam nadzieję, że ktoś ją znajdzie i połączy :-)
Dimitris Fasarakis Hilliard
4
@ user1803551 Javadocs nie wspomina o różnicy między szczegółami implementacji tych dwóch metod (takich jak zużycie miejsca lub wydajność). Myślę, że ludzie też chcieliby poznać te szczegóły.
ZhekaKozlov
5
@ZhekaKozlov Przyjęta i bardzo pozytywnie oceniona odpowiedź też nie. Co to mówi o przyjętych standardach? Zawiera nawet mniej informacji niż w dokumentacji (serializacja, tożsamość, porządkowanie). Jeśli już, wyślij prośbę do OpenJDK o dodanie tych informacji.
user1803551
3
To pytanie jest omawiane na meta .
Dimitris Fasarakis Hilliard

Odpowiedzi:

173

Arrays.asListzwraca zmienną listę, podczas gdy lista zwrócona przez List.ofjest niezmienna :

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.asListdopuszcza elementy puste, podczas gdy List.ofnie:

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

contains zachowuje się inaczej w przypadku wartości null:

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.asListzwraca widok przekazanej tablicy, więc zmiany w tablicy zostaną również odzwierciedlone na liście. Do List.oftego nie jest prawdą:

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]
ZhekaKozlov
źródło
22
Lista, która zachowuje się inaczej w zależności od tego, jak została skonstruowana, nie wydaje mi się zbyt zorientowana obiektowo. Może gdyby List.of zwrócił typ ImmutableList, miałoby to sens. To jest bardzo nieszczelna abstrakcja.
Sandy Chapman
5
Nie jestem programistą Java, więc potraktuj to jako zwykłą obserwację. Prawdopodobnie istnieje dobry powód, dla którego zachowanie się różni, ale gdybym miał metodę zwracającą List <Integer>, jak na przykładzie, interfejs nie byłby wystarczający, abym wiedział, czy otrzymam wyjątek czasu wykonywania, jeśli go sprawdzę dla wartości null. Podobnie zmiana w implementacji tej metody może wpłynąć na kod oddalony od miejsca wywołania mojej metody, jeśli to sprawdzenie nastąpi w innym miejscu. @Nicolai
Sandy Chapman,
8
@SandyChapman może to być nieoczekiwane zachowanie dla niektórych (lub większości?), Ale jest to udokumentowane zachowanie. Z List.contains(Object o)javadoc 's : "Zgłasza [...] NullPointerException - jeśli określony element ma wartość null i ta lista nie zezwala na elementy puste (opcjonalne)". Lub z obszernego wprowadzenia do interfejsu, o którym niewielu mówi: „Niektóre implementacje kolekcji mają ograniczenia dotyczące elementów, które mogą zawierać”
Aaron
11
@Aaron cóż, przynajmniej jest to dobrze udokumentowana nieszczelna abstrakcja :)
Sandy Chapman
6
@Sandy Chapman: List.of nie powrócić jakiś ImmutableListtyp, jego rzeczywista nazwa to tylko drobny szczegół realizacja niepubliczne. Jeśli była publiczna i ktoś Listponownie ją przesłał, jaka była różnica? Gdzie jest różnica Arrays.asList, która zwraca niepubliczną Listimplementację, która zgłasza wyjątek podczas próby addlub remove, lub lista zwrócona, Collections.unmodifiableListktóra nie pozwala na żadną modyfikację? Chodzi o kontrakty określone w Listinterfejsie. Interfejsy kolekcji z opcjonalnymi metodami zawsze były nieczytelnym OOP od Java 1.2…
Holger
31

Różnice między Arrays.asListiList.of

Zobacz JavaDocs i ten wykład autorstwa Stuarta Marksa (lub jego poprzednich wersji).

Będę używał następujących przykładów kodu:

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

Niezmienność strukturalna (lub: niemodyfikowalność)

Każda próba zmiany strukturalnejList.of spowoduje, że plik UnsupportedOperationException. Obejmuje to operacje takie jak dodawanie , ustawianie i usuwanie . Możesz jednak zmienić zawartość obiektów na liście (jeśli obiekty nie są niezmienne), więc lista nie jest „całkowicie niezmienna”.

To samo dotyczy niemodyfikowalnych list utworzonych za pomocą Collections.unmodifiableList. Tylko ta lista jest widokiem oryginalnej listy, więc może się zmienić, jeśli zmienisz oryginalną listę.

Arrays.asListnie jest całkowicie niezmienna, nie ma ograniczeń set.

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

Podobnie zmiana tablicy bazowej (jeśli ją przytrzymasz) spowoduje zmianę listy.

Niezmienność strukturalna wiąże się z wieloma skutkami ubocznymi związanymi z kodowaniem obronnym, współbieżnością i bezpieczeństwem, które wykraczają poza zakres tej odpowiedzi.

Zerowa wrogość

List.ofi wszystkie kolekcje od wersji Java 1.5 nie pozwalają nulljako element. Próba przekazania nulljako elementu lub nawet wyszukiwania spowoduje utworzenie pliku NullPointerException.

Ponieważ Arrays.asListjest to kolekcja z wersji 1.2 (struktura kolekcji), umożliwia nulls.

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

Formularz serializowany

Ponieważ List.ofzostała wprowadzona w Javie 9, a listy utworzone za pomocą tej metody mają własną (binarną) zserializowaną postać, nie można ich deserializować we wcześniejszych wersjach JDK (brak zgodności binarnej ). Możesz jednak na przykład de / serializować za pomocą JSON.

Tożsamość

Arrays.asListwywołania wewnętrzne new ArrayList, co gwarantuje nierówność odniesień.

List.ofzależy od implementacji wewnętrznej. Zwrócone instancje mogą mieć równość odwołań, ale ponieważ nie jest to gwarantowane, nie można na nich polegać.

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

Warto wspomnieć, że listy są równe (via List.equals), jeśli zawierają te same elementy w tej samej kolejności, niezależnie od tego, jak zostały utworzone lub jakie operacje obsługują.

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

Implementacja (ostrzeżenie: szczegóły mogą ulec zmianie w przypadku wersji)

Jeśli liczba elementów na liście List.ofwynosi 2 lub mniej, elementy są przechowywane w polach wyspecjalizowanej (wewnętrznej) klasy. Przykładem jest lista przechowująca 2 elementy (częściowe źródło):

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);
    }
}

W przeciwnym razie są przechowywane w tablicy w podobny sposób jak Arrays.asList.

Efektywność czasu i przestrzeni

W List.ofimplementacji, które są oparte na polu (wielkość <2) nieznacznie szybciej wykonywać niektóre operacje. Na przykład size()może zwrócić stałą bez pobierania długości tablicy i contains(E e)nie wymaga narzutu iteracji.

Tworzenie niemodyfikowalnej listy przez List.ofjest również szybsze. Porównaj powyższy konstruktor z 2 przypisaniami referencyjnymi (a nawet jednym dla dowolnej liczby elementów) do

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

co tworzy 2 listy plus inne narzuty. Jeśli chodzi o miejsce, oszczędzasz UnmodifiableListopakowanie i kilka groszy. Ostatecznie oszczędności w HashSetekwiwalencie są bardziej przekonujące.


Czas zakończenia: użyj, List.ofgdy chcesz mieć listę, która się nie zmienia i Arrays.asListkiedy chcesz, aby lista mogła się zmieniać (jak pokazano powyżej).

user1803551
źródło
1
Osoby zastanawiające się, dlaczego istnieje taka odpowiedź, zobacz to .
user1803551
3
Arrays.asListnie jest w pełni zmienny. asList.add(1);rzuca UnsupportedOperationException.
mapeters
„Zero wrogie” to świetny sposób na określenie tego. Właściwie nie mogę wykorzystać List.ofczasu, w którym ludzie chcieliby zadzwonić containsi nie być zaskoczonym wyjątkiem NullPointerException.
Noumenon
14

Podsumujmy różnice między List.of i Arrays.asList

  1. List.ofmoże być najlepiej stosowany, gdy zestaw danych jest mniejszy i niezmieniony, natomiast Arrays.asListmoże być najlepiej używany w przypadku dużego i dynamicznego zestawu danych.

  2. List.ofzajmuje bardzo mniej miejsca narzutu, ponieważ ma implementację opartą na polach i zużywa mniej miejsca na sterty, zarówno pod względem stałego narzutu, jak i na podstawie liczby elementów. natomiast Arrays.asListzająć więcej miejsca nad głową, ponieważ podczas inicjalizacji tworzy więcej obiektów w sterty.

  3. Kolekcja zwrócona przez List.ofjest niezmienna i dlatego jest bezpieczna wątkowo, podczas gdy kolekcja zwrócona przez Arrays.asListjest modyfikowalna i nie jest bezpieczna wątkowo. (Niezmienne wystąpienia kolekcji zwykle zużywają znacznie mniej pamięci niż ich zmienne odpowiedniki).

  4. List.ofnie zezwala na puste elementy, podczas gdy Arrays.asListzezwala na puste elementy.

Mohit Tyagi
źródło
2
„Niezmienne instancje kolekcji zwykle zajmują znacznie mniej pamięci niż ich mutowalne odpowiedniki”. - Naprawdę? Czy zechciałbyś trochę się nad tym rozwinąć - czy masz na myśli to, że można je bezpiecznie udostępniać, czy też masz na myśli, że same instancje można w jakiś sposób efektywniej zaimplementować?
Hulk
1
@Hulk Odpowiadający ma rację co do wydajności przestrzeni. Zobacz Stuarta znaków Dyskusja: youtu.be/q6zF3vf114M?t=49m48s
ZhekaKozlov
2
@ZhekaKozlov Wydaje się, że ogólnie jest to prawdą, ale jestem bardzo sceptyczny, że to prawda, gdy mówię o Arrays.asListversus List.of, biorąc pod uwagę, że to pierwsze jest dosłownie tylko opakowaniem wokół tablicy. Przynajmniej wydaje się, że implementacja OpenJDK ma bardzo mały narzut. W rzeczywistości List.ofmusiałby wykonać kopie dowolnej przekazanej tablicy, więc jeśli sama tablica nie będzie wkrótce GC, wydawałoby się, że List.ofma znacznie większy ślad pamięci.
Chris Hayes,
4
@ChrisHayes Przynajmniej List.of(x)i List.of(x, y)są bardziej wydajne, ponieważ w ogóle nie przydzielają tablic
ZhekaKozlov
2
@Hulk: nie zapominaj, że List.ofmetody nie są wymagane do zwracania nowych list za każdym razem. Te listy mają nieokreśloną tożsamość, więc na poziomie maszyny JVM może być obsługiwane buforowanie, deduplikacja lub skalaryzacja. Jeśli nie w tej wersji, to może w następnej. Kontrakt na to pozwala. W przeciwieństwie do tego, Array.asListzależy od tożsamości przekazanej tablicy, ponieważ wynikowa lista jest zmiennym widokiem tablicy, odzwierciedlającym wszystkie zmiany dwukierunkowo.
Holger
3

Oprócz powyższych odpowiedzi są pewne operacje na oba List::ofi Arrays::asListróżnią:

+----------------------+---------------+----------+----------------+---------------------+
|      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. ✔️ oznacza, że ​​metoda jest obsługiwana
  2. ❌ oznacza, że ​​wywołanie tej metody spowoduje zgłoszenie wyjątku UnsupportedOperationException
  3. ❗️ oznacza, że ​​metoda jest obsługiwana tylko wtedy, gdy argumenty metody nie powodują mutacji, np. Collections.singletonList ("foo"). RetainAll ("foo") jest OK, ale Collections.singletonList ("foo"). RetainAll ("bar" ) zgłasza UnsupportedOperationException

Więcej o kolekcjach :: singletonList Vs. Lista

Vishwa Ratna
źródło
1
odpowiedź na egzamin Java
povis