Backed Collections и Array-backed списки в Java - Вопросы
Всего: 6 вопросов
1. Что такое backed collection (представление, view) в Java и какие методы возвращают представления?
Что такое 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() метод 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>?
Что вернёт 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?
Как удалить диапазон элементов списка через 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() ключа вне диапазона?
Как работает 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(), 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(...).