W3docs

Интерфейс List в Java

Упорядоченные коллекции с доступом по индексу в Java: интерфейс List и его основные операции.

List<E> — это Collection<E> плюс два дополнительных обязательства: элементы имеют определённый порядок, и этот порядок адресуется целочисленным индексом. Как только есть порядок и индекс, становится осмысленным целый класс методов — get(i), set(i, x), indexOf(x), subList(from, to), сортировка, итерация в обратном порядке. Эта глава рассматривает контракт; реализации (ArrayList, LinkedList, Vector) следуют сразу после со своими характеристиками производительности.

Что означает «упорядоченный» в данном контексте

«Упорядоченный» применительно к List означает, что порядок вставки сохраняется — индекс 0 — это первый добавленный элемент, индекс size() - 1 — последний, а добавление нового элемента в конец ничего не сдвигает. Это не «отсортированный» — список сохраняет тот порядок, который вы создаёте. Если нужна сортированная итерация, вызовите Collections.sort(list) (изменяет список) или используйте TreeSet / TreeMap с самого начала. Не путайте эти понятия.

Дубликаты разрешены. [1, 1, 2, 1] — вполне допустимый List<Integer>.

Методы, которые List добавляет к Collection

Всё, что объявляет Collection, по-прежнему присутствует — add, remove, contains, size и т. д. List добавляет позиционные и порядко-зависимые операции:

Позиционный доступ

  • E get(int index) — элемент по индексу index.
  • E set(int index, E element) — заменить, возвращая старое значение.
  • void add(int index, E element) — вставить (сдвигает следующие элементы вправо).
  • E remove(int index) — удалить по позиции (возвращает удалённый элемент). Обратите внимание на перегрузку с Objectlist.remove(1) вызывает версию с int; list.remove(Integer.valueOf(1)) вызывает версию с Object.

Поиск

  • int indexOf(Object o) — первое вхождение или -1.
  • int lastIndexOf(Object o) — последнее вхождение или -1.

Подпредставления и итерация

  • List<E> subList(int fromIndex, int toIndex)живое представление диапазона. Изменение его изменяет исходный список (и наоборот). Полуоткрытый интервал: [from, to).
  • ListIterator<E> listIterator() / listIterator(int index) — итератор, который также может идти в обратном направлении, получать текущий индекс и выполнять set / add на позиции курсора. Глава ListIterator описывает его подробнее.

Массовые изменения с учётом порядка

  • default void replaceAll(UnaryOperator<E> op) — применить op к каждому элементу на месте.
  • default void sort(Comparator<? super E> c) — отсортировать список, используя c (или естественный порядок, если null).
  • boolean addAll(int index, Collection<? extends E> c) — вставить целую коллекцию начиная с позиции index.

Фабрики (Java 9+)

  • List.of(...) — неизменяемый список из заданных элементов. Компактный, без лишних аллокаций для небольших размеров.
  • List.copyOf(Collection) — неизменяемый снимок другой коллекции.

Равенство List зависит от порядка

Два списка равны тогда и только тогда, когда они имеют одинаковый размер, одинаковый порядок и равные элементы на каждом индексе. List.of(1, 2) не равен List.of(2, 1), даже если как Set-ы они были бы равны. Это жёсткое правило контракта List — если вы сравниваете два списка и получаете false, когда «не должны», проверьте порядок в первую очередь.

subList — это представление, а не копия

С этим сталкивается почти каждый, кто учится:

List<Integer> xs = new ArrayList<>(List.of(0, 1, 2, 3, 4, 5));
List<Integer> middle = xs.subList(2, 5);    // [2, 3, 4]
middle.set(0, 99);
System.out.println(xs);          // [0, 1, 99, 3, 4, 5]   — xs changed!
middle.clear();
System.out.println(xs);          // [0, 1, 5]            — gone from xs

subList возвращает живое окно. Чтения и записи проходят через исходный список. Это очень полезно для алгоритмов на месте — очистить диапазон, отсортировать диапазон, вставить диапазон — но это также означает, что нельзя хранить ссылку на subList и при этом изменять родительский список через любой другой путь. В Javadoc сказано, что структурные изменения исходного списка за пределами подсписка «неопределяют» поведение подсписка. На практике — ConcurrentModificationException при следующем же обращении.

Если нужен независимый срез, скопируйте его: new ArrayList<>(xs.subList(2, 5)).

Две перегрузки remove

Распространённая ошибка:

List<Integer> nums = new ArrayList<>(List.of(10, 20, 30));
nums.remove(1);                       // removes index 1 → [10, 30]
nums.remove(Integer.valueOf(10));     // removes the value 10 → [30]

Перегрузка с int побеждает, потому что int более специфичен, чем Integer. Если вы хотите «удалить значение 10», оберните его явно. Это одно из немногих мест в языке, где правила автобоксинга и разрешение перегрузки активно конфликтуют.

Сортировка на месте

list.sort(comparator) изменяет список. Передайте null, чтобы использовать естественный порядок элементов (их Comparable); иначе передайте Comparator. Это современная форма — Collections.sort(list) по-прежнему работает и идентична, но list.sort(...) является методом по умолчанию в List:

List<String> names = new ArrayList<>(List.of("Linus", "Ada", "Grace"));
names.sort(null);                              // natural: ["Ada", "Grace", "Linus"]
names.sort(Comparator.comparingInt(String::length));  // shortest first

Глава Comparable / Comparator далее в этой части является справочником по тому, что означает null и как строить компараторы для собственных типов.

Неизменяемые фабрики: когда add выбрасывает исключение

List.of(...), List.copyOf(...) и списки, возвращаемые Collectors.toUnmodifiableList(), являются неизменяемыми. Они отклоняют каждый изменяющий вызов с UnsupportedOperationException. Они также не допускают элементы null. Они идеальны для данных только для чтения, используемых повсеместно:

List<String> CONSTANTS = List.of("red", "green", "blue");
CONSTANTS.add("yellow");    // throws UnsupportedOperationException

Если вы планируете мутировать список позже, начинайте с new ArrayList<>(List.of(...)).

Практический пример: все методы, специфичные для List

Программа ниже демонстрирует методы, которые List добавляет к Collection. Обратите внимание на то, как мутация через subList распространяется на исходный список, на ловушку перегрузки и на разницу между sort и replaceAll.

java— editable, runs on the server

Несколько выводов из результатов, которые стоит запомнить:

  • remove(1) удалил 20 (значение по индексу 1); remove(Integer.valueOf(10)) удалил 10 по значению. Одно имя метода — две разные задачи в зависимости от статического типа аргумента.
  • После mid.clear() родительский список равен [0, 1, 5]. Представление и было тем диапазоном — его очистка удалила эти элементы из исходного массива.
  • replaceAll сохраняет длину списка и перезаписывает каждый элемент на месте; sort переставляет уже имеющиеся элементы. Они хорошо сочетаются друг с другом.

Что дальше

Теперь вы знаете контракт List — что гарантируется, что изменяет что, где скрываются подводные камни. Пришло время познакомиться с реализацией, которую вы будете использовать в 90% случаев: ArrayList на основе массива с изменяемым размером. Тот же контракт, конкретные характеристики производительности и несколько собственных дополнений.

Практика

Практика
`xs` равен `new ArrayList<>(List.of(10, 20, 30, 40))`. Вы вызываете `xs.subList(1, 3).clear()`. Каким будет `xs` после этого?
`xs` равен `new ArrayList<>(List.of(10, 20, 30, 40))`. Вы вызываете `xs.subList(1, 3).clear()`. Каким будет `xs` после этого?
Was this page helpful?