ArrayList: примеры (JAVA)
ArrayListОбщее описание класса ArrayList
Класс ArrayList из пакета java.util представляет собой реализованную на массиве динамическую реализацию интерфейса List. Применяется в случаях, когда требуется последовательная коллекция с быстрым доступом по индексу и частыми операциями добавления в конец. Характерные свойства: упорядоченность по вставке, допуск null-элементов, не синхронизирован по умолчанию.
Основные конструкторы и их параметры:
ArrayList()- создаёт пустой список с начальными внутренними настройками вместимости.ArrayList(int initialCapacity)- выделяет внутренний массив указанной initialCapacity. При частом добавлении большого числа элементов выгодно задать ёмкость заранее.ArrayList(Collection<? extends E> c)- создаёт список с элементами из переданной коллекции; ёмкость соответствует размеру коллекции.
Ключевые методы, сигнатуры, аргументы и возвращаемые значения:
boolean add(E e)- добавляет элемент в конец. Возвращаетtrue(в ArrayList всегдаtrue, если добавление прошло без исключений).void add(int index, E element)- вставляет элемент по индексу; индекс должен быть в интервале [0, size]. При нарушении возникаетIndexOutOfBoundsException.E get(int index)- возвращает элемент по индексу; при неверном индексе выбрасываетсяIndexOutOfBoundsException.E set(int index, E element)- заменяет элемент по индексу и возвращает старое значение.E remove(int index)- удаляет элемент по индексу и возвращает удалённое значение.boolean remove(Object o)- удаляет первое вхождение объекта и возвращаетtrue, если элемент был найден и удалён.int size()- возвращает текущее число элементов.boolean isEmpty()- проверка на пустоту.boolean contains(Object o)- поиск по равенствуequals.Iterator<E> iterator()иListIterator<E> listIterator()- итераторы; при модификации списка вне итератора может возникнутьConcurrentModificationException.Object[] toArray()и<T> T[] toArray(T[] a)- преобразование в массив; при вызове вторым вариантом возвращается массив типа T.boolean addAll(Collection<? extends E> c)иboolean addAll(int index, Collection<? extends E> c)- массовое добавление; возвращаютtrue, если коллекция изменилась.void clear()- удаляет все элементы, размер становится 0.int indexOf(Object o)иint lastIndexOf(Object o)- поиск индекса первого и последнего вхождения, возвращают -1 если не найдено.- Java 8+ методы:
void forEach(Consumer<? super E> action),boolean removeIf(Predicate<? super E> filter),void replaceAll(UnaryOperator<E> operator),void sort(Comparator<? super E> c)- модификации и обход с функциональными интерфейсами. void ensureCapacity(int minCapacity)- запрос увеличения внутренней ёмкости, полезно для производительности при большом числе добавлений.void trimToSize()- сокращение внутреннего массива до фактического размера.Object clone()- поверхностная копия ArrayList; элементы не клонируются.
Исключения и гарантии: операции с индексами выбрасывают IndexOutOfBoundsException. Итераторы проверяют модификации за счёт fail-fast механизма, при некорректных параллельных изменениях возникает ConcurrentModificationException. ArrayList не обеспечивает потокобезопасность без внешней синхронизации.
Короткие примеры применения
Создание и базовые операции:
import java.util.ArrayList;
ArrayList list = new ArrayList<>();
list.add("one");
list.add("two");
list.add(1, "inserted");
System.out.println(list);
System.out.println(list.get(2));
list.set(0, "uno");
System.out.println(list.size());
[uno, inserted, two] two 3
Удаление и проверка наличия:
ArrayList nums = new ArrayList<>(java.util.Arrays.asList(1,2,3,2));
boolean removed = nums.remove(Integer.valueOf(2));
System.out.println(removed);
System.out.println(nums);
true [1, 3, 2]
Преобразование в массив и обратное добавление:
String[] arr = list.toArray(new String[0]);
System.out.println(java.util.Arrays.toString(arr));
ArrayList copy = new ArrayList<>(java.util.Arrays.asList(arr));
System.out.println(copy);
[uno, inserted, two] [uno, inserted, two]
Сортировка и removeIf (Java 8+):
ArrayList words = new ArrayList<>(java.util.Arrays.asList("z", "a", "m"));
words.sort(java.util.Comparator.naturalOrder());
words.removeIf(s -> s.equals("m"));
System.out.println(words);
[a, z]
Похожие коллекции в Java и особенности
- LinkedList - реализует List на двусвязном списке. Предпочтительнее при частых вставках/удалениях в середине коллекции, но доступ по индексу медленнее.
- Vector - устаревший аналог ArrayList с синхронизацией на уровне методов. Возможна рекомендация в старом коде, но для новых проектов лучше использовать коллекции из java.util.concurrent при необходимости потокобезопасности.
- CopyOnWriteArrayList - потокобезопасная реализация, оптимизирована для большого числа параллельных чтений и редких записей. Подходит для сценариев с малым числом модификаций.
- Collections.unmodifiableList и List.of - возвращают неизменяемые представления; используются при передаче данных, которые не должны изменяться.
- При выборе между ArrayList и LinkedList следует учитывать характер операций: частый доступ по индексу - ArrayList, частые вставки/удаления в середине - LinkedList.
Аналоги в других языках и отличия
Ниже примеры эквивалентных структур в разных языках и краткие замечания.
- JavaScript -
Array. Динамический, поддерживает push/pop/ splice. Отличие: в JS массивы более гибкие, нет строго типизации.
let a = [1,2];
a.push(3);
a.splice(1,0,9);
console.log(a);
[1,9,2,3]
list. Аналог ArrayList по поведению, динамический и допускает любые типы.lst = [1,2]
lst.append(3)
lst.insert(1,9)
print(lst)
[1, 9, 2, 3]
List<T> в System.Collections.Generic. Очень похож по API; методы Add, Insert, Remove, ToArray.var list = new System.Collections.Generic.List<int>{1,2};
list.Add(3);
list.Insert(1,9);
Console.WriteLine(string.Join(",", list));
1,9,2,3
s := []int{1,2}
s = append(s, 3)
// вставка
s = append(s[:1], append([]int{9}, s[1:]...)...)
fmt.Println(s)
[1 9 2 3]
$a = [1,2];
array_splice($a, 1, 0, 9);
print_r($a);
Array
(
[0] => 1
[1] => 9
[2] => 2
)
local t = {1,2}
table.insert(t,2,9)
for i,v in ipairs(t) do print(v) end
1 9 2
Типичные ошибки и примеры
Частые ошибки при работе с ArrayList и возможные последствия:
- IndexOutOfBoundsException при доступе по неверному индексу:
ArrayList<String> a = new ArrayList<>();
a.add("x");
System.out.println(a.get(1));
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 1, Size: 1
at java.util.ArrayList.rangeCheckForGet(ArrayList.java:xxx)
...
- ConcurrentModificationException при одновременной модификации списка во время обхода for-each:
ArrayList<Integer> nums = new ArrayList<>(java.util.Arrays.asList(1,2,3));
for (Integer n : nums) {
if (n == 2) nums.remove(n);
}
System.out.println(nums);
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:xxx)
...
Рекомендация: при необходимости удаления в цикле применять Iterator.remove() или collect-and-remove.
- Неожиданные результаты при использовании Arrays.asList(...) - полученная коллекция имеет фиксированный размер и не поддерживает add/remove:
java.util.List<String> fixed = java.util.Arrays.asList("a","b");
fixed.add("c");
Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.AbstractList.add(AbstractList.java:xxx)
...
Избежание: обернуть в новый ArrayList, если требуется изменяемая копия.
Также возможны ClassCastException при некорректных приведениях типов при использовании сырых типов или небезопасных преобразований массива через toArray.
Изменения и дополнения в последних версиях Java
Класс ArrayList сохраняет стабильный API, но в Java 8 и выше появились методы, улучшающие удобство работы:
- Java 8: добавлены
forEach,removeIf,replaceAll,sort. Это упростило применение лямбда-выражений и функциональных операций. - Java 9+: фабричные методы
List.ofи иные улучшения коллекций влияют на сценарии создания списков (возвращают неизменяемые списки). ArrayList как реализация изменений API не претерпевала. - Оптимизации внутренней работы JVM и реализации коллекций происходили постепенно, но семантика методов ArrayList осталась обратносвместимой.
Расширенные и редкие сценарии использования
1) Оптимизация выделения памяти при большом числе добавлений:
ArrayList<Integer> big = new ArrayList<>(1_000_000);
for (int i = 0; i < 1_000_000; i++) big.add(i);
System.out.println(big.size());
1000000
Комментарий: заранее заданная ёмкость уменьшает число перераспределений внутреннего массива.
2) Использование subList как представления части списка и подводные камни:
ArrayList<String> src = new ArrayList<>(java.util.Arrays.asList("a","b","c","d"));
java.util.List<String> view = src.subList(1,3);
view.set(0, "B");
System.out.println(src);
view.add("x");
System.out.println(src);
src.add("y"); // модификация исходного списка нарушит view при дальнейших операциях
[a, B, c, d] [a, B, c, x, d] // последующие операции с view могут вызвать ConcurrentModificationException
Комментарий: subList возвращает представление, изменения синхронизируются; однако структурные изменения родителя могут привести к ошибкам при дальнейшей работе с подсписком.
3) Потокобезопасные варианты: синхронизация и альтернативы
// синхронизация вручную
java.util.List<String> sync = java.util.Collections.synchronizedList(new ArrayList<><>());
// CopyOnWriteArrayList для сценариев read-mostly
java.util.concurrent.CopyOnWriteArrayList<String> cow = new java.util.concurrent.CopyOnWriteArrayList<>();
cow.add("a");
for (String s : cow) System.out.println(s);
a
Комментарий: CopyOnWriteArrayList копирует внутреннюю структуру при каждой записи, что даёт безопасность итераций, но дорого при частых модификациях.
4) shallow clone и клонирование элементов:
ArrayList<MyObj> orig = new ArrayList<>();
orig.add(new MyObj("x"));
ArrayList<MyObj> cloned = (ArrayList<MyObj>) orig.clone();
cloned.get(0).setName("y");
System.out.println(orig.get(0).getName());
y
Комментарий: clone делает поверхностную копию; если требуется глубокое клонирование, требуется копировать элементы вручную.
5) Параллельные операции через Stream API и влияние на исходный список:
ArrayList<Integer> nums = new ArrayList<>(java.util.Arrays.asList(5,3,1,4));
java.util.List<Integer> sorted = nums.stream().sorted().toList();
System.out.println(sorted);
// оригинал не изменяется
System.out.println(nums);
[1, 3, 4, 5] [5, 3, 1, 4]
Комментарий: stream().sorted() возвращает новый список; для изменения исходного можно вызвать nums.sort(...).
6) Сортировка с кастомным компаратором и стабилизация:
class Item { String id; int pr; Item(String i,int p){id=i;pr=p;} public String toString(){return id+":"+pr;}}
ArrayList<Item> items = new ArrayList<>();
items.add(new Item("a",2)); items.add(new Item("b",1)); items.add(new Item("c",2));
items.sort(java.util.Comparator.comparingInt((Item it) -> it.pr).thenComparing(it -> it.id));
System.out.println(items);
[b:1, a:2, c:2]
Комментарий: комбинация компараторов позволяет контролировать порядок при равных ключах.
7) Поведение роста внутреннего массива (алгоритм расширения):
При добавлении при нехватке места новая ёмкость обычно равна oldCapacity + (oldCapacity >> 1) (приблизительно 1.5x). Это влияет на использование памяти и количество копирований.