Backed Collections и Array-backed списки в Java - Вопросы

Всего: 6 вопросов

1. 

Что такое backed collection (представление, view) в Java и какие методы возвращают представления?

Backed collection (представление, view) — коллекция без собственного хранилища: все операции чтения и записи она перенаправляет в исходную коллекцию или массив. Изменения источника сразу видны через представление, а изменения через представление попадают в источник. Создаётся представление за O(1), ничего не копируя. Array-backed список — частный случай, когда источник — массив.

Представления возвращают:

  • Arrays.asList(array) — представление массива фиксированного размера;
  • list.subList(from, to) — часть списка с индексами [from, to);
  • map.keySet(), values(), entrySet() — удаление из них удаляет пару из карты, add() бросает UnsupportedOperationException;
  • headSet()/subSet()/tailSet() и headMap()/subMap()/tailMap() — диапазоны TreeSet и TreeMap;
  • descendingMap(), descendingSet(), list.reversed() (Java 21) — та же коллекция в обратном порядке;
  • обёртки Collections.unmodifiableList() и другие — только чтение, но изменения исходной коллекции через них видны.
2. 

Почему у списка из Arrays.asList() метод set() работает, а add() и remove() бросают UnsupportedOperationException?

Arrays.asList(array) возвращает список фиксированного размера, который хранит ссылку на сам массив, а не его копию. Длину массива изменить нельзя, поэтому всё, что меняет размер, запрещено, а всё, что работает на месте, разрешено и меняет массив:

  • get(), size(), contains(), indexOf() — читают массив;
  • set(i, e), list.sort(...), Collections.shuffle(list) — работают и меняют массив; запись array[i] = ... тоже сразу видна в списке;
  • add(), add(i, e), remove(int), addAll() — UnsupportedOperationException;
  • remove(Object), removeIf(), clear() — исключение, только если есть что удалять: remove("груша") для отсутствующего элемента вернёт false.

Класс такого списка — java.util.Arrays$ArrayList, закрытый вложенный класс Arrays, а не java.util.ArrayList: list instanceof ArrayList даёт false. В отличие от List.of(), элемент null допустим. Если нужен изменяемый список, сделайте копию: new ArrayList<>(Arrays.asList(array)).

3. 

Что вернёт Arrays.asList(new int[]{1, 2, 3}) и как превратить int[] в List<Integer>?

Он вернёт List<int[]> из одного элемента — самого массива, поэтому size() равен 1. Обобщённый параметр T в asList(T... a) не может быть примитивом, и весь int[] становится одним аргументом varargs. Строка List<Integer> list = Arrays.asList(new int[]{1, 2, 3}) не скомпилируется.

С Integer[] или перечислением значений Arrays.asList(1, 2, 3) получится список из трёх чисел. Для массива примитивов используйте стрим:

int[] ints = {1, 2, 3};
List<Integer> fixed = Arrays.stream(ints).boxed().toList();                        // Java 16, неизменяемый
List<Integer> mutable = Arrays.stream(ints).boxed().collect(Collectors.toList()); // изменяемый

Результат стрима — копия: связи с исходным массивом у него нет.

4. 

Как удалить диапазон элементов списка через subList() и когда подсписок бросает ConcurrentModificationException?

list.subList(from, to) — представление элементов с индекса from включительно до to не включительно. Всё, что делается через подсписок (set(), add(), remove(), clear()), сразу отражается в исходном списке. Поэтому стандартный способ удалить диапазон — list.subList(from, to).clear():

List<String> list = new ArrayList<>(List.of("a", "b", "c", "d", "e"));
list.subList(1, 4).clear();
System.out.println(list); // [a, e]

Если же изменить размер исходного списка в обход подсписка (list.add(), list.remove()), подсписок становится недействительным: на любое обращение (get(), size(), toString()) он отвечает ConcurrentModificationException. Замена через list.set() структурным изменением не считается и подсписок не ломает.

Неверные границы проверяются сразу: subList(3, 1) — IllegalArgumentException, subList(0, 10) для списка из пяти элементов — IndexOutOfBoundsException. Подсписок списка из Arrays.asList() тоже фиксированного размера: его clear() бросит UnsupportedOperationException. И помните, что подсписок держит ссылку на весь исходный список: долгоживущий фрагмент копируйте через new ArrayList<>(list.subList(0, 10)).

5. 

Как работает subMap() у TreeMap как backed-коллекция и что будет при put() ключа вне диапазона?

map.subMap(fromKey, toKey) возвращает представление ключей от fromKey включительно до toKey не включительно. Записи, добавленные в исходную карту внутри диапазона, появляются в представлении, а записи, добавленные или удалённые через представление, меняют исходную карту. Ключи вне диапазона в представлении не видны.

SortedMap<String, String> map = new TreeMap<>(Map.of("а", "арбуз", "в", "вишня"));
SortedMap<String, String> sub = map.subMap("б", "ж");
map.put("б", "брусника"); // видно в sub
sub.put("г", "груша");    // попало в map
map.put("я", "яблоко");   // в sub не видно
sub.put("с", "слива");    // IllegalArgumentException: key out of range

Добавить через представление ключ вне его диапазона нельзя: put() бросает IllegalArgumentException: key out of range — такой ключ добавляют в исходную карту. Границы проверяются по порядку сортировки, а строки сравниваются по кодам символов: sub.put("ё", "ёлка") тоже упадёт, потому что код «ё» (U+0451) больше кода «ж» (U+0436). Так же ведут себя headMap() и tailMap().

6. 

Чем представление отличается от копии: какие из Arrays.asList(), new ArrayList<>(...), List.of(), List.copyOf() и Collections.unmodifiableList() видят изменения источника?

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

  • Arrays.asList(array) — представление: после array[0] = "яблоко" список тоже изменится;
  • new ArrayList<>(Arrays.asList(array)) — изменяемая копия;
  • List.of(array) (Java 9) и List.copyOf(list) (Java 10) — неизменяемые копии: изменения массива или списка-источника в них не видны;
  • Collections.unmodifiableList(list) — наоборот, представление только для чтения: менять через него нельзя, но изменения исходного list через него видны.

Неизменяемость и связь с источником — разные свойства: List.of() неизменяем, но не является представлением. Если нужен независимый «снимок» любого представления, скопируйте его: new ArrayList<>(list.subList(1, 4)), new TreeMap<>(map.subMap("б", "ж")), new HashSet<>(map.keySet()) или List.copyOf(...).

Страница 1 из 1