Если коротко: ArrayList это список на основе массива из пакета java.util. От обычного массива он отличается тем, что не требует заранее знать размер: когда внутренний массив заполняется, создается новый побольше и данные переезжают туда. Элементы хранятся по порядку, доступны по индексу и могут повторяться. Ниже разобраны все три конструктора класса и его основные методы, каждый с примером. List это упорядоченный набор объектов, каждый элемент которого занимает определенную позицию в списке. Интерфейс List расширяет интерфейс Collection и добавляет в него несколько методов для работы со списками, таких как методы доступа к элементам по их положению в списке и методы поиска и сортировки списков. List может содержать повторяющиеся элементы, доступ к этим элементам можно получить по их положению в списке. Интерфейс List обеспечивает удобный способ работы с упорядоченными коллекциями элементов. Он обычно используется, когда вам нужно поддерживать порядок элементов в коллекции или когда вам нужно получить доступ к элементам по их индексу в списке. При разработке часто бывает сложно предсказать, какого размера понадобятся массивы. Поэтому функция динамического выделения памяти во время работы программы необходима каждому языку программирования. Динамическим называется массив, размер которого может измениться во время исполнения программы. В Java для такой цели существует класс ArrayList.

Кратко

  • ArrayList это реализация интерфейса List поверх массива, который сам растет по мере добавления элементов.
  • Хранит только ссылочные типы. Для чисел и прочих примитивов берут классы-обертки: Integer вместо int.
  • Конструктора три: пустой с начальной емкостью 10, из другой коллекции и с заданной начальной емкостью.
  • Доступ по индексу через get() и set() идет прямо в ячейку и не зависит от размера списка, а вставка и удаление в середине сдвигают весь хвост.
  • Методы делятся на группы: добавление (add, addAll), удаление (remove, removeAll, clear), поиск (contains, indexOf), обход (iterator, forEach) и преобразование (toArray, clone).
  • Итератор у ArrayList работает по принципу fail-fast: если менять список во время обхода в обход самого итератора, полетит ConcurrentModificationException.

Что такое класс ArrayList?

ArrayList это реализация изменяемого массива интерфейса List, часть Collection Framework, который отвечает за список (или динамический массив), расположенный в пакете java.util. Этот класс реализует все необязательные операции со списком и предоставляет методы управления размером массива, который используется для хранения списка. В основе ArrayList лежит идея динамического массива. А именно, возможность добавлять и удалять элементы, при этом будет увеличиваться или уменьшаться по мере необходимости.

Что хранит ArrayList?

Только ссылочные типы, любые объекты, включая сторонние классы. Строки, потоки вывода, другие коллекции. Для хранения примитивных типов данных используются классы-обертки.

Конструкторы ArrayList

ArrayList()

Пустой конструктор с начальной емкостью внутреннего массива = 10.

ArrayList<String> list = new ArrayList<>();

В угловых скобках желательно указать тип хранимых значений. В примере выше это String.

ArrayList(Collection <? extends E> c)

Конструктор принимает другую коллекцию, создавая новый массив с элементами переданной коллекции:

ArrayList<String> list2 = new ArrayList<>(list);

Порядок элементов в новом списке будет совпадать с исходным.

ArrayList(int initialCapacity)

В качестве параметра конструктора выступает значения начального размера внутреннего массива.

ArrayList<String> list2 = new ArrayList<>(10000);

Если в массиве, который лежит в основе ArrayList, закончилось место при добавлении новых элементов, создается новый массив большего размера, и данные копируются в него. Если при написании кода заранее известно, что в массиве будет обрабатываться большое количество элементов, в целях оптимизации следует указать большее значение.

Методы ArrayList

Ниже представлены основные методы ArrayList.

add(E e)

Добавляет новый элемент в конец списка. Возвращает boolean, причем у ArrayList это всегда true: так задано контрактом Collection.add. Значение нужно тем коллекциям, которые могут отказаться принять элемент, например Set вернет false на дубликате.

ArrayList<String> list = new ArrayList<>();
list.add("Hello");

add(int index, E element)

Добавляет элемент element в позицию index. При добавлении происходит сдвиг всех элементов справа от указанного индекса на 1 позицию вправо:

list.add(0, "Amigo");

Очень полезен, когда нужно вставить элемент в произвольное место списка, однако для частых операций вставки в начало и середину ArrayList может оказаться не очень удачным выбором: тут стоит присмотреться к LinkedList.

addAll(Collection <? extends E> collection)

Добавление всех элементов коллекции collection в список в порядке их расположения в collection.

addAll(int index, Collection <? extends E> collection)

Добавление всех элементов collection в список начиная с индекса index. При этом все элементы сдвинутся вправо на количество элементов в списке collection:

ArrayList<String> secondList = new ArrayList<>();
secondList.addAll(list);
System.out.println("Первое добавление: " + secondList);
secondList.addAll(1, list);
System.out.println("Второе добавление в середину: " + secondList);

Вывод:


Первое добавление: [Amigo, Hello]
Второе добавление в середину: [Amigo, Amigo, Hello, Hello]

Методы addAll() также возвращают boolean-результат добавления элементов.

clear()

Удаление всех элементов из списка.

clone()

Возвращает объект-копию массива:

ArrayList<String> copyOfSecondList = (ArrayList<String>) secondList.clone();
secondList.clear();
System.out.println(copyOfSecondList);

Вывод:


[Amigo, Amigo, Hello, Hello]

Следует обратить внимание, что метод clone() возвращает Object, так что после его вызова потребуется сделать приведение к необходимому классу.

Копия получается поверхностной: новыми будут сам список и его внутренний массив, а элементы не копируются, оба списка ссылаются на одни и те же объекты. В примере это и видно: очистка исходного списка не сказалась на составе копии, потому что массив у копии свой.

contains(Object o)

Проверка наличия объекта в списке, возвращает boolean-значение.

System.out.println(copyOfSecondList.contains("Hello"));
System.out.println(copyOfSecondList.contains("Check"));

Вывод:


true
false

ensureCapacity(int minCapacity)

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

Этот метод полезен, когда возникает потребность вместить большое количество элементов в несколько итераций. Например, при создании списка емкость его внутреннего массива равна 10. При загрузке данных по сети они обрабатываются асинхронно порциями и результаты помещаются в массив. Если ожидается доставка 10 000 элементов, может быть неэффективно просто добавлять эти данные каждый раз: достаточно будет в начале обработки вызвать метод ensureCapacity(10000) и записывать туда данные по мере необходимости.

forEach(Consumer<? super E> action)

Обработать в цикле ArrayList можно стандартными способами, цикл for:

// Первый способ
for(int i = 0; i< secondList.size(); i++) {
   System.out.println(secondList.get(i));
}

И цикл for-each:

// Второй способ
for(String s : secondList) {
   System.out.println(s);
}

В классе ArrayList есть метод для обработки каждого элемента, который называется также, forEach. В качестве аргумента передается реализация интерфейса Consumer, в котором нужно переопределить метод accept():

secondList.forEach(new Consumer<String>() {
   @Override
   public void accept(String s) {
       System.out.println(s);
   }
});

Вывод:


Amigo
Amigo
Hello
Hello

Метод accept принимает в качестве аргумента очередной элемент того типа, который хранит в себе ArrayList. Пример для Integer:

ArrayList<Integer> integerList = new ArrayList<>();
integerList.forEach(new Consumer<Integer>() {
   @Override
   public void accept(Integer integer) {
       System.out.println(integer);
   }
});

Метод accept() будет выполнен для каждого элемента: action это имя параметра, в который передают реализацию Consumer.

get(int index)

Возвращает элемент, который расположен в указанной позиции списка.

Если index < 0 или index >= максимального количества элементов списка, будет выброшено исключение IndexOutOfBoundsException.

Это основной метод получения элемента из списка, время извлечения элемента по индексу всегда будет одинаковым, независимо от размера ArrayList.

indexOf(Object o)

Метод возвращает индекс первого вхождения элемента в списке. Если элемента не существует в списке, метод вернет -1.

isEmpty()

Метод возвращает true, если список пустой, false в обратном случае.

Если в списке содержатся только элементы null, метод вернет false. Иными словами, null элементы также учитываются этим методом.

iterator()

Возвращает итератор для списка для последующего использования в цикле или при любой другой обработке.

Итератор для ArrayList это fail-fast. Это значит, что если коллекция изменится во время итерации, будет выброшено исключение ConcurrentModificationException. Подробнее об fail-fast и его противоположности fail-safe можно почитать здесь.

lastIndexOf(Object o)

Функционал метода похож на indexOf(), отличие в том, что возвращается индекс последнего элемента в списке.

Если элемент не найден, также возвращает -1.

remove(int index)

Удаление элемента в указанной позиции индекса. После удаления сдвигает все элементы влево для заполнения освободившегося пространства.

Если index<0 или >= количество элементов списка, будет выброшено исключение IndexOutOfBoundsException. В результате метод возвращает элемент, который был удален.

remove(Object o)

Метод удаляет из списка переданный элемент o. Если элемент присутствует в списке, он удаляется, а все элементы смещаются влево. Если элемент существует в списке и успешно удален, метод возвращает true, в обратном случае false.

removeAll(Collection<?> c)

Если необходимо удалить несколько элементов, не стоит делать это в цикле по условию: гораздо удобнее и безопаснее воспользоваться методом removeAll(). Он принимает коллекцию элементов, которая будет удалена из списка.

Коллекция должна содержать элементы того же типа, которые хранит целевой список. В обратном случае будет выброшен ClassCastException. Метод вернет true, если список был изменен в результате вызова метода.

set(int index, E element)

Замена элемента в указанной позиции index на переданный element. Индекс должен быть не меньше нуля и строго меньше размера списка, то есть от нуля до индекса последнего элемента включительно, иначе будет выброшено исключение IndexOutOfBoundsException.

size()

Лучший способ (практически единственный) для того, чтобы узнать размер списка, то есть количество реально лежащих в нем элементов. Длина внутреннего массива может быть больше.

sort(Comparator<? super E> c)

Сортировка списка по заданному правилу. Правило это реализация интерфейса Comparator с методом compare(a, b): он сравнивает два элемента и возвращает отрицательное число, ноль или положительное в зависимости от того, какой из них должен идти раньше.

Свой компаратор нужен, если коллекция содержит объекты вашего класса или если требуется необычный порядок. У стандартных классов вроде Integer и String естественный порядок уже задан методом compareTo() интерфейса Comparable, так что для обычной сортировки хватит Collections.sort(list).

toArray()

Превращает список в фиксированный массив. Обратите внимание, что метод возвращает массив объектов (Object[]). Если необходимо привести список в массив объектов определенного типа, в качестве параметра в метод можно передать массив, куда будут перемещены элементы списков.

Пример:

String[] array = new String[secondList.size()];
secondList.toArray(array);
for(int i = 0; i< array.length; i++) {
   System.out.println(array[i]);
}

Вывод:


Amigo
Amigo
Hello
Hello

ArrayList или LinkedList

Выше пара раз мелькнуло, что для частых вставок в начало списка ArrayList подходит плохо. Вот в чем разница между двумя реализациями List.
ArrayList LinkedList
Доступ по индексу, get(i) Мгновенный: обращение к ячейке массива, размер списка не важен Придется идти по цепочке узлов от ближайшего края
Добавление в конец Обычно мгновенное, изредка дороже: при нехватке места массив пересоздается Всегда мгновенное: создается один узел
Вставка и удаление в начале или середине Дорого: System.arraycopy() сдвигает весь хвост списка Сама операция дешевая, переставляются две ссылки, но дойти до нужного места все равно надо
Память на один элемент Только ссылка, плюс запас пустых ячеек в массиве Ссылка плюс два указателя на соседние узлы
Когда брать Почти всегда: чтение по индексу, обход, добавление в конец Когда список постоянно меняется с краев, LinkedList реализует еще и Deque

Коротко: ArrayList держит элементы в одном массиве подряд, поэтому мгновенно достает любой по индексу, но при вставке в середину двигает весь хвост. LinkedList по документации это двусвязный список: каждый элемент лежит в своем узле и знает соседей, поэтому вставка сама по себе дешевая, зато добраться до нужного индекса можно только пройдя по цепочке от ближайшего края. На практике ArrayList выигрывает почти всегда, а LinkedList берут, когда список работает как очередь или дека. Подробнее про вторую реализацию в отдельной статье.

Методы ArrayList в Java изучаются на курсе JavaRush. Первое знакомство происходит на седьмом уровне квеста Java Syntax, на лекции “Класс ArrayList”. На этом же уровне есть подборки задач, раз и два, в которых нужно использовать методы ArrayList, приведены дополнительные примеры работы с ArrayList и дженериками, а также объясняется разница между ArrayList и LinkedList. Это обширная тема изучения, поэтому в том или ином виде к ArrayList в Java (методы этого класса это лишь часть всего массива знаний, в который стоит углубиться) на курсе возвращаются и на следующих уровнях обучения: Core, Collections, Multithreading. Мы верим, что ежедневная практика написания кода это главный ключ к успеху в программировании. Поэтому JavaRush на 80% состоит из практических задач, мини-проектов, задач-игр. Все это сотни часов кодинга, которые помогут прокачать скилл.

Вопросы и ответы

Что такое ArrayList в Java и в каком пакете он лежит?

ArrayList это класс из пакета java.util, реализация интерфейса List на основе обычного массива. Список хранит элементы по порядку, разрешает дубликаты и дает доступ по индексу. Главное отличие от массива в том, что размер задавать заранее не нужно: когда внутренний массив заполнен, класс сам создает массив побольше и переносит туда данные.

Можно ли хранить в ArrayList примитивы вроде int?

Напрямую нет, ArrayList работает только со ссылочными типами. Вместо int в него кладут Integer, вместо double будет Double и так далее. Синтаксически это незаметно, потому что компилятор сам заворачивает число в обертку, но помнить об этом стоит: у обертки есть накладные расходы по памяти, а сравнивать такие значения надо через equals(), а не через ==.

Какая емкость у нового ArrayList и зачем нужен ensureCapacity()?

У списка, созданного пустым конструктором, начальная емкость внутреннего массива равна 10. Когда место заканчивается, создается новый массив и все элементы копируются в него, и это самая дорогая операция в классе. ensureCapacity(int minCapacity) позволяет зарезервировать место заранее и тем самым избежать череды таких копирований. Если заранее известно, что элементов будет, скажем, десять тысяч, разумнее сразу задать емкость через конструктор или вызвать ensureCapacity().

Как отсортировать ArrayList?

Если элементы уже умеют сравниваться сами, как Integer или String, достаточно Collections.sort(list). Для своего порядка нужен Comparator: это интерфейс с одним методом compare(a, b), который возвращает отрицательное число, ноль или положительное в зависимости от того, какой из элементов должен идти раньше. Готовый компаратор передают в list.sort(comparator). Не путайте его с Comparable: там метод называется compareTo() и задает естественный порядок самого класса.

Что возвращает add() и всегда ли добавление успешно?

У ArrayList метод add(E e) всегда возвращает true. Так прямо и написано в документации класса: результат true задан контрактом Collection.add. Значение boolean существует ради тех коллекций, которые могут отказаться принять элемент, например Set вернет false при попытке добавить дубликат. Проверять результат add() у списка смысла нет: если места не хватит, вы получите не false, а OutOfMemoryError.

Почему при удалении элементов в цикле вылетает ConcurrentModificationException?

Потому что итератор ArrayList работает по принципу fail-fast. Он запоминает счетчик изменений списка и сверяет его на каждом шаге. Если вызвать list.remove() прямо внутри цикла for-each, счетчик разойдется с ожидаемым, и полетит исключение. Удалять во время обхода нужно методом самого итератора iterator.remove(), а если условие удаления простое, проще всего обойтись removeIf().

Читайте также

Ссылки на дополнительное чтение

  1. Подробная статья о динамических массивах, а точнее об ArrayList и LinkedList, которые выполняют их роль в языке Java.
  2. Статья об удалении элементов из списка ArrayList.
  3. Лекция о работе с ArrayList в схемах и картинках.