Чим відрізняється List.of від Arrays.asList?


117

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

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

Яка різниця між попереднім та новим варіантом? Тобто, в чому різниця між цим:

Arrays.asList(1, 2, 3);

і це:

List.of(1, 2, 3);

1
Дивіться також цю розмову Стюарта "Beaker" Маркса.
user1803551

20
@ user1803551 Хоча я розумію ваше розчарування, це міркування може стати справді небажаним прецедентом. На багато питань тут є відповідь, яка "чітко прописана" (залежно від того, як це визначено). Я б закликав вас донести цю дискусію до мета, але я впевнений, що така дискусія вже має існувати (і я сподіваюся, що хтось може її знайти і зв’язати :-)
Димитріс Фасаракіс Хілліард

4
@ user1803551 Javadocs не згадують про різницю між деталями впровадження цих двох методів (наприклад, споживання місця або продуктивність). Я думаю, що люди також хотіли б знати ці деталі.
Жека Козлов

5
@ZhekaKozlov Також не прийнята і надвисока відповідь. Що це говорить про прийняті стандарти? У ньому навіть менше інформації, ніж у документах (серіалізація, особистість, замовлення). Якщо що-небудь, подайте запит до OpenJDK, щоб додати цю інформацію.
user1803551

3
Це питання обговорюється на мета .
Димитріс Фасаракіс Хілліард

Відповіді:


173

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]

22
Для того, щоб список поводився інакше, виходячи з того, як він побудований, мені здається не дуже об’єктно орієнтованим. Можливо, якби List.of повернув тип ImmutableList, це мало б сенс. Тут дуже просочена абстракція.
Сенді Чапман

5
Я не розробник Java, тому сприймайте це як випадкове спостереження. Можливо, є вагома причина, щоб поведінка відрізнялася, але якби у мене був метод повернення списку <Integer>, як, наприклад, інтерфейс був би недостатнім для мене, якщо я знаю, чи отримаю виняток із виконання часу, якщо перевіряю його для нулів. Так само зміна в реалізації методів може вплинути на код, віддалений від сайту виклику мого методу, якщо ця перевірка відбудеться в іншому місці. @Nicolai
Sandy Chapman

8
@SandyChapman це може бути несподіваною поведінкою для деяких (або більшості?), Але це документоване поведінка. З List.contains(Object o)'s javadoc : "Кидає [...] NullPointerException - якщо вказаний елемент є нульовим і цей список не дозволяє нульових елементів (необов'язково)". Або з тривалого вступу інтерфейсу, який мало хто читає: "Деякі реалізації колекції мають обмеження на елементи, які вони можуть містити"
Аарон

11
@Aaron добре, принаймні, це добре задокументована просочена абстракція :)
Sandy Chapman

6
@Sandy Chapman: List.of дійсно повертають деякий ImmutableListтип, його фактичну назву просто деталь непублічної реалізацій. Якщо вона була загальнодоступною і хтось кинув її Listзнову, де була різниця? Де різниця Arrays.asList, яка повертає непублічну Listреалізацію, яка викидає виняток при спробі addабо remove, або список, повернений, Collections.unmodifiableListякий не дозволяє змінювати взагалі? Вся справа в контрактах, зазначених в Listінтерфейсі. Інтерфейси колекцій з додатковими методами завжди були нечистими OOP з Java 1.2…
Holger

31

Відмінності між Arrays.asListіList.of

Дивіться JavaDocs і цю розмову Stuart Marks (або попередні версії).

Я буду використовувати наступні приклади коду:

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 (Framework Collection), вона дозволяє 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коли ви хочете змінити список (як показано вище).


1
Люди, які цікавляться, чому ця відповідь існує, дивіться це .
user1803551

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 Відповідач правий щодо просторової ефективності. Дивіться розмову Стюарта Маркса: youtu.be/q6zF3vf114M?t=49m48s
Жека Козлов

2
@ZhekaKozlov Це, мабуть, правда в цілому, але я дуже скептично налаштований, що це правда, коли говорити про Arrays.asListпроти List.of, враховуючи, що колишня буквально просто обгортка навколо масиву. Принаймні , впровадження OpenJDK має надзвичайно невеликі витрати. Насправді, List.ofпотрібно було б зробити копії будь-якого переданого масиву, тому, якщо сам масив незабаром не стане GC'd, здавалося б, List.ofмає значно більший слід пам’яті.
Кріс Хейс

4
Принаймні List.of(x)і List.of(x, y)ефективніші @ChrisHayes, тому що вони взагалі не виділяють масиви
Жека Козлов

2
@Hulk: не забувайте, що List.ofметоди не потрібно щоразу повертати нові списки. У цих списках не визначена ідентичність, тому на рівні JVM може бути кешоване чи дедупликаційне чи скаларизоване. Якщо ні в цій версії, то, можливо, в наступній. Це дозволено договором. Навпаки, це Array.asListзалежить від ідентичності масиву, який ви передаєте, оскільки отриманий список є змінним видом на масив, відображаючи всі зміни в двосторонній формі.
Холгер

2

Крім вищезазначених відповідей, існують певні операції, за якими 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" ) викидає непідтримуванеOperationException

Більше про колекції :: singletonList Vs. Список


1
відповідь на іспит на java
povis
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.