В чем разница между List.of и Arrays.asList?

118

В Java 9 появились новые фабричные методы для списков List.of:

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

В чем разница между предыдущим и новым вариантом? То есть в чем разница между этим:

Arrays.asList(1, 2, 3);

и это:

List.of(1, 2, 3);
ZhekaKozlov
источник
1
См. Также это выступление Стюарта «Стакан» Маркс.
user1803551 05
20
@ user1803551 Хотя я понимаю ваше разочарование, это рассуждение может создать действительно нежелательный прецедент. На многие вопросы здесь есть «четко сформулированный» ответ (в зависимости от того, как это определить). Я бы посоветовал вам перенести это обсуждение на мета, но я уверен, что такое обсуждение уже должно существовать (и я надеюсь, что кто-то сможет найти его и связать его :-)
Димитрис Фасаракис Хиллиард
4
@ user1803551 В документации Javadocs не упоминается разница между деталями реализации этих двух методов (такими как потребление места или производительность). Думаю, люди тоже хотели бы знать эти подробности.
ЖекаКозлов 05
5
@ZhekaKozlov В принятом и получившем супервизу ответе тоже нет. Что это говорит вам о принятых стандартах? В нем даже меньше информации, чем в документации (сериализация, идентификация, упорядочивание). Если что, отправьте запрос в OpenJDK, чтобы добавить эту информацию.
user1803551 06
3
Этот вопрос обсуждается на мета .
Димитрис Фасаракис Хиллиард

Ответы:

175

Arrays.asListвозвращает изменяемый список, в то время как список, возвращаемый функцией,List.of является неизменным :

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.asListдопускает нулевые элементы, а List.ofне:

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

contains по-другому ведет себя с нулями:

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.asListвозвращает представление переданного массива, поэтому изменения в массиве также будут отражены в списке. Ибо List.ofэто неправда:

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
источник
22
То, что список ведет себя по-разному в зависимости от того, как он построен, мне не кажется очень объектно-ориентированным. Возможно, если бы List.of вернул тип ImmutableList, это имело бы смысл. Это очень дырявая абстракция.
Сэнди Чепмен
5
Я не Java-разработчик, так что воспринимайте это как случайное наблюдение. Возможно, есть веская причина для различий в поведении, но если бы у меня был метод, возвращающий List <Integer>, как в примере, интерфейса было бы недостаточно, чтобы я знал, получу ли я исключение времени выполнения, если я его проверю. для нулей. Точно так же изменение в реализации этих методов может повлиять на код, удаленный от места вызова моего метода, если эта проверка произойдет в другом месте. @Nicolai
Сэнди Чепмен
8
@SandyChapman это может быть неожиданным поведением для некоторых (или для большинства?), Но это задокументированное поведение. Из документации List.contains(Object o)javadoc : «Вызывает [...] исключение NullPointerException - если указанный элемент является нулевым и этот список не допускает нулевые элементы (необязательно)». Или из длинного введения интерфейса, которое немногие читают: «Некоторые реализации коллекций имеют ограничения на элементы, которые они могут содержать»
Аарон
11
@Aaron ну, по крайней мере, это хорошо задокументированная дырявая абстракция :)
Sandy Chapman
6
@Sandy Chapman: List.of действительно возвращают некоторый ImmutableListтип, его фактическое название просто деталь непубличной реализаций. Если он был публичным и кто-то Listснова его закинул, в чем разница? В чем разница Arrays.asList, которая возвращает непубличную Listреализацию, которая выдает исключение при попытке addили remove, или список, возвращаемый Collections.unmodifiableListwhich, не допускает никаких изменений вообще? Все дело в контрактах, указанных в Listинтерфейсе. Интерфейсы коллекций с необязательными методами всегда были нечистыми ООП, начиная с Java 1.2…
Holger
31

Различия между Arrays.asListиList.of

См. JavaDocs и доклад Стюарта Маркса (или его предыдущие версии).

Для примеров кода я буду использовать следующее:

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

Структурная неизменность (или: неизменяемость)

Любая попытка структурных изменений List.ofприведет к созданию файла UnsupportedOperationException. Сюда входят такие операции, как добавление , установка и удаление . Однако вы можете изменить содержимое объектов в списке (если объекты не являются неизменяемыми), чтобы список не был «полностью неизменяемым».

То же самое и с неизменяемыми списками, созданными с помощью Collections.unmodifiableList. Только этот список является представлением исходного списка, поэтому он может измениться, если вы измените исходный список.

Arrays.asListне является полностью неизменяемым, для него нет ограничений set.

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

Точно так же изменение массива поддержки (если вы его удерживаете) изменит список.

Структурная неизменяемость имеет множество побочных эффектов, связанных с защитным кодированием, параллелизмом и безопасностью, которые выходят за рамки этого ответа.

Нулевая враждебность

List.ofи любая коллекция, начиная с Java 1.5, не допускает использования nullв качестве элемента. Попытка пройтиnull как элемент или даже поиск приведет к созданию файла NullPointerException.

Поскольку Arrays.asListэто коллекция из 1.2 (платформа коллекций), она позволяет nulls.

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

Сериализованная форма

Поскольку List.ofон был представлен в Java 9 и списки, созданные этим методом, имеют свою собственную (двоичную) сериализованную форму, они не могут быть десериализованы в более ранних версиях JDK (нет двоичной совместимости ). Однако вы можете де / сериализовать, например, с помощью JSON.

тождественность

Arrays.asListвнутренние вызовы new ArrayList, что гарантирует ссылочное неравенство.

List.ofзависит от внутренней реализации. Возвращенные экземпляры могут иметь ссылочное равенство, но поскольку это не гарантируется, вы не можете на это полагаться.

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

Стоит отметить, что списки равны (через List.equals), если они содержат одинаковые элементы в одном порядке, независимо от того, как они были созданы или какие операции они поддерживают.

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

Реализация (предупреждение: детали могут изменяться в зависимости от версии)

Если количество элементов в списке List.ofравно 2 или меньше, элементы сохраняются в полях специализированного (внутреннего) класса. Примером может служить список, в котором хранятся 2 элемента (частичный источник):

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

В противном случае они хранятся в массиве аналогично Arrays.asList.

Эффективность времени и пространства

В List.ofреализации , которые являются полями на основе (размером <2) немного быстрее выполнить на некоторых операциях. Например, size()может возвращать константу без извлечения длины массива и contains(E e)не требует дополнительных затрат на итерацию.

Создание неизменяемого списка с помощью List.ofтакже выполняется быстрее. Сравните приведенный выше конструктор с двумя ссылочными назначениями (и даже с одним для произвольного количества элементов) для

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

который создает 2 списка плюс другие накладные расходы. Что касается места, вы экономите UnmodifiableListобертку плюс несколько копеек. В конечном итоге экономия в HashSetэквиваленте более убедительна.


Время заключения: используйте, List.ofкогда вам нужен список, который не меняется, и Arrays.asListкогда вам нужен список, который может изменяться (как показано выше).

user1803551
источник
1
Для тех, кто задается вопросом, почему существует этот ответ, см. Это .
user1803551 06
3
Arrays.asListне полностью изменяемый. asList.add(1);бросает UnsupportedOperationException.
mapeters
"Ничто не враждебно" - отличный способ выразиться. Я почти не могу использовать List.ofвремя, когда люди могут захотеть позвонить containsи не удивиться исключению NullPointerException.
Нуменон
14

Пусть суммировать различия между List.of и Arrays.asList

  1. List.ofлучше всего использовать, когда набор данных меньше и не изменяется, а Arrays.asListлучше всего использовать в случае большого и динамического набора данных.

  2. List.ofзанимают очень меньше служебного пространства, потому что он реализован на основе полей и потребляет меньше места в куче, как с точки зрения фиксированных служебных данных, так и для отдельных элементов. в то время как Arrays.asListзанимают больше служебного пространства, потому что при инициализации он создает больше объектов в куче.

  3. Коллекция, возвращаемая List.ofобъектом, является неизменной и, следовательно, потокобезопасной, в то время как Коллекция, возвращаемая Arrays.asListобъектом, является изменяемой и не потокобезопасной. (Неизменяемые экземпляры коллекций обычно потребляют гораздо меньше памяти, чем их изменяемые аналоги.)

  4. List.ofне допускает нулевые элементы, а Arrays.asListдопускает нулевые элементы.

Мохит Тьяги
источник
2
«Неизменяемые экземпляры коллекций обычно потребляют гораздо меньше памяти, чем их изменяемые аналоги». - В самом деле? Не могли бы вы подробнее остановиться на этом - вы имеете в виду, потому что ими можно безопасно делиться, или вы имеете в виду, что сами экземпляры можно как-то реализовать более эффективно?
Халк
1
@Hulk Ответчик прав насчет экономии места. См. Выступление
ЖекаКозлов 05
2
@ZhekaKozlov В целом это кажется правдой, но я очень скептически отношусь к тому, что это правда, когда речь идет о Arrays.asListversus List.of, учитывая, что первое - это буквально просто оболочка вокруг массива. По крайней мере , реализация OpenJDK имеет чрезвычайно небольшие накладные расходы. Фактически, List.ofнеобходимо будет сделать копии любого переданного массива, поэтому, если сам массив не собирается в ближайшее время быть GC, похоже, List.ofимеет значительно больший объем памяти.
Крис Хейс
4
@ChrisHayes По крайней мере, List.of(x)и List.of(x, y)более эффективны, потому что они вообще не выделяют массивы
ЖекаКозлов 05
2
@Hulk: не забывайте, что List.ofметоды не обязаны каждый раз возвращать новые списки. Эти списки имеют неопределенную идентичность, поэтому на уровне JVM может выполняться кэширование, дедупликация или скаляризация. Если не в этой версии, то, возможно, в следующей. Это разрешено договором. Напротив, это Array.asListзависит от идентификатора массива, который вы передаете, так как результирующий список является изменяемым представлением массива, отражающим все изменения в двух направлениях.
Хольгер
3

Помимо приведенных выше ответов, существуют определенные операции, по которым оба List::ofи Arrays::asListразличаются:

+----------------------+---------------+----------+----------------+---------------------+
|      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. ✔️ означает, что метод поддерживается
  2. ❌ означает, что вызов этого метода вызовет исключение UnsupportedOperationException
  3. ❗️ означает, что метод поддерживается только в том случае, если аргументы метода не вызывают мутации, например, Collections.singletonList ("foo"). KeepAll ("foo") в порядке, но Collections.singletonList ("foo"). KeepAll ("bar" ) выдает исключение UnsupportedOperationException

Подробнее о Коллекциях :: singletonList Vs. Список

Вишва Ратна
источник
1
ответ на экзамен java
povis