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

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

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

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

Arrays.asList(1, 2, 3);

и это:

List.of(1, 2, 3);

Ответ 1

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]

Ответ 2

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

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

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

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

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

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

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

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

Так как Arrays.asList представляет собой набор из 1.2 (Framework Collections), он позволяет null s.

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, если вам нужен список, который может измениться (как показано выше).

Ответ 3

Обобщите различия между List.of и Arrays.asList

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

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

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

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

Ответ 4

Помимо приведенных выше ответов, существуют определенные операции, в которых отличаются 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"). RetainAll ("foo") в порядке, но Collections.singletonList("foo"). RetainAll ("bar") создает UnsupportedOperationException

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