List
Определение и контракт
List<E> — упорядоченная коллекция. Элемент имеет позицию от 0 до size() - 1; дубликаты обычно разрешены. Контракт списка включает как последовательный обход, так и операции по индексу.
List<String> steps = new ArrayList<>();
steps.add("validate");
steps.add("save");
steps.add("save");
String second = steps.get(1); // "save"Самые употребимые операции:
| Операция | Методы |
|---|---|
| Доступ | get, set, indexOf, lastIndexOf |
| Вставка | add(value), add(index, value), addAll |
| Удаление | remove(index), remove(object), removeIf |
| Обход | iterator, listIterator, forEach |
| Диапазон | subList(fromInclusive, toExclusive) |
| Сортировка | sort |
ArrayList и LinkedList
ArrayList хранит ссылки в расширяемом массиве:
getиset—O(1);- добавление в конец — амортизированное
O(1); - вставка или удаление в середине —
O(n)из-за сдвига; - компактное размещение обычно хорошо использует кэш процессора.
LinkedList хранит двусвязную цепочку узлов:
- доступ по индексу и поиск —
O(n); - добавление с известных концов —
O(1); - удаление через уже установленный итератор —
O(1); - на каждый элемент нужны дополнительные ссылки.
На практике ArrayList — исходный выбор для большинства списков. Фраза «LinkedList быстрее вставляет в середину» неполна: сначала позицию обычно приходится искать за O(n).
subList — представление, а не копия
subList возвращает вид на диапазон исходного списка:
List<String> source = new ArrayList<>(
List.of("A", "B", "C", "D")
);
List<String> middle = source.subList(1, 3);
middle.set(0, "X");
System.out.println(source); // [A, X, C, D]Изменение через подсписок отражается в исходном списке. Структурное изменение исходника в обход subList делает дальнейшую работу представления неопределённой контрактом и обычно приводит к ConcurrentModificationException.
Для независимого результата нужна копия:
List<String> snapshot = new ArrayList<>(source.subList(1, 3));Эта особенность однажды проявилась в реальной задаче: Кейс subList изменил исходный список.
Изменяемость
List<String> mutable = new ArrayList<>();
List<String> fixedSize = Arrays.asList("a", "b");
List<String> immutable = List.of("a", "b");
List<String> copy = List.copyOf(mutable);ArrayListможно структурно менять.Arrays.asList— представление фиксированного размера над массивом:setразрешён,addиremove— нет.List.ofиList.copyOfсоздают немодифицируемые списки и не допускаютnull.
Перегрузка remove
У List<Integer> выражение remove(1) выбирает перегрузку по индексу:
List<Integer> values = new ArrayList<>(List.of(1, 2, 3));
values.remove(Integer.valueOf(1)); // удалить значение 1ListIterator
ListIterator умеет двигаться в обе стороны, узнавать соседние индексы и безопасно изменять список через сам итератор:
ListIterator<String> it = steps.listIterator();
while (it.hasNext()) {
if (it.next().isBlank()) {
it.remove();
}
}Ловушки
- Путать индекс и значение в
remove. - Хранить
subListдольше исходного списка или считать его копией. - Выбирать
LinkedListпо одной только асимптотике вставки. - Изменять обычный список внутри enhanced
for. - Возвращать внутренний mutable-список из объекта и тем самым отдавать наружу его состояние.
Вопросы на собеседовании
- Какие гарантии добавляет
Listк контрактуCollection? - Почему добавление в конец
ArrayListамортизированноO(1)? - Когда
LinkedListдействительно может быть уместен? - Чем отличаются
List.of,Arrays.asListиnew ArrayList<>()? - Как устроен
subListи почему с ним легко получить неожиданный побочный эффект? - Как удалить число
1изList<Integer>, а не элемент с индексом1?
Связанные заметки
Collection · Иерархия коллекций · Big O оценка сложности операций с коллекциями · Queue and Deque · Comparator Java · Кейс subList изменил исходный список