ArrayList в Java — практическое руководство
Что такое ArrayList
ArrayList — это реализуемая на основе массива реализация интерфейса List из пакета java.util. Она хранит элементы в упорядоченном виде и автоматически изменяет размер при добавлении или удалении элементов. Ключевые возможности:
- Доступ по индексу (get/set).
- Динамическое изменение размера (добавление/удаление).
- Поддержка коллекционных операций (addAll, removeAll и т. д.).
Определение: ArrayList — это изменяемый, индексируемый контейнер для объектов в Java.
Основные подклассы AbstractList
AbstractList реализует List и служит базой для нескольких реализаций коллекций. Близкие классы:
- LinkedList — оптимизирован для вставки и удаления в середине списка.
- Vector — похож на ArrayList, но синхронизирован; подходит для многопоточных окружений старого кода.
- Stack — устаревшая структура, расширяет Vector и обеспечивает LIFO-операции.
Эти классы выходят за рамки данного руководства. Здесь мы сосредоточимся на общем ArrayList.
Изображение: диаграмма иерархии классов коллекций Java с выделением ArrayList и его родственников.
Создание ArrayList
Создать ArrayList очень просто. Сначала импортируйте пакет java.util.ArrayList (или java.util.*). Далее можно использовать один из конструкторов.
Без параметров (пустой список):
ArrayList alist = new ArrayList(); Если заранее известна примерная ёмкость, можно указать initial capacity. Это рекомендация для внутреннего массива и иногда даёт небольшое преимущество по производительности при больших объёмах данных.
ArrayList alist = new ArrayList(8); Изображение: схематичное представление нового ArrayList с выделенными пустыми индексами.
Наполнение ArrayList
Добавление элементов в конец
Для добавления одиночного элемента в конец используйте add(). Пример:
ArrayList alist = new ArrayList();
alist.add("apple");
alist.add("banana");
alist.add("cantaloupe");
alist.add("orange");
System.out.println(alist);
Вывод в консоль:
[apple, banana, cantaloupe, orange]Метод size() возвращает текущее количество элементов:
System.out.println("Number of elements in the arraylist: " + alist.size());
// Number of elements in the arraylist: 4Изображение: визуализация добавления элементов в конец динамического массива.
Добавление по указанному индексу
Чтобы вставить элемент на конкретную позицию, используйте перегруженный add(index, item):
alist.add(3, "grapes");
System.out.println(alist);
Вывод:
[apple, banana, cantaloupe, grapes, orange]Помните: индексация начинается с нуля.
Добавление коллекции целиком
Вы можете добавить все элементы из другой коллекции через addAll(). Например:
List items = Arrays.asList("pear", "cherry");
alist.addAll(items);
System.out.println(alist);
// [apple, banana, cantaloupe, grapes, orange, pear, cherry] Также можно указать индекс, куда вставить коллекцию: alist.addAll(2, items);
Доступ к элементам
Доступ по индексу
Если известен индекс, используйте get():
String item = alist.get(2);
System.out.println("Item at index 2 is: " + item);
// Item at index 2 is: cantaloupeПоиск элемента
indexOf() возвращает индекс первого вхождения или -1, если элемент не найден:
int index = alist.indexOf("orange");
if (index < 0) {
System.out.println("Item \"orange\" not found");
} else {
System.out.println("Item \"orange\" found at index " + index);
}
// Item "orange" found at index 4Если элемента нет, indexOf вернёт -1.
Итерация по ArrayList
Частая операция — проход по всем элементам. Самые распространённые способы:
- Enhanced for loop (foreach):
for (String fruit : alist) {
System.out.println("Found fruit \"" + fruit + "\"");
}- Iterator: позволяет безопасно удалять элементы во время прохода. В примере ниже создаётся копия списка, и на ней выполняется итерация с удалением элементов, начинающихся на “c”:
ArrayList blist = new ArrayList(alist);
for (Iterator iter = blist.iterator() ; iter.hasNext() ; ) {
String fruit = iter.next();
if (fruit.startsWith("c")) {
iter.remove();
} else {
System.out.println("Keeping \"" + fruit + "\"");
}
}
// Keeping "apple"
// Keeping "banana"
// Keeping "grapes"
// Keeping "orange"
// Keeping "pear" Важно: нельзя изменять коллекцию напрямую (например, alist.remove(i)) во время итерации с использованием foreach — это приведёт к ConcurrentModificationException.
Замена и удаление элементов
Замена
set(index, item) заменяет элемент по индексу:
alist.set(5, "pineapple");
System.out.println(alist);
// [apple, banana, cantaloupe, grapes, orange, pineapple, cherry]Удаление
remove(index) возвращает удалённый элемент, remove(object) возвращает boolean — true, если элемент был найден и удалён:
String fruit = alist.remove(2);
System.out.println("Removed element at 2: " + fruit);
// Removed element at 2: cantaloupe
fruit = "grapes";
System.out.println("Remove " + fruit + " from the list? " + alist.remove(fruit));
// Remove grapes from the list? trueПрактическая ценность и выбор структуры данных
ArrayList отлично подходит, когда вам нужен быстрый доступ по индексу и часто выполняются операции чтения или добавления в конец. Однако существуют сценарии, когда он не лучший выбор.
Когда не стоит использовать ArrayList
- Частые вставки/удаления в середине большого списка (LinkedList или специализированные структуры лучше).
- Требуется хранение пар «ключ-значение» (используйте Map, например HashMap).
- Жёсткие требования по многопоточности: лучше использовать ConcurrentLinkedQueue/CopyOnWriteArrayList или синхронизацию.
Альтернативы
- LinkedList — эффективен для вставок/удалений в середине списка.
- HashMap/TreeMap — для доступа по ключу.
- ArrayDeque — для стека/очереди с быстрой работой с концами.
- CopyOnWriteArrayList — для сценариев с частыми чтениями и очень редкими модификациями в многопоточном окружении.
Ментальные модели и эвристики выбора
- Если 80% операций — чтение и случайный доступ, выбирайте ArrayList.
- Если 50% операций — вставки/удаления в середине, выбирайте LinkedList.
- Если нужен уникальный ключ для каждого элемента — используйте Map.
Шпаргалка по методам ArrayList
| Метод | Что делает |
|---|---|
| add(E e) | Добавляет элемент в конец |
| add(int index, E e) | Вставляет элемент по индексу |
| addAll(Collection c) | Добавляет все элементы из коллекции |
| get(int index) | Возвращает элемент по индексу |
| set(int index, E e) | Заменяет элемент по индексу |
| remove(int index) | Удаляет и возвращает элемент по индексу |
| remove(Object o) | Удаляет первый найденный объект, возвращает boolean |
| indexOf(Object o) | Возвращает индекс или -1 |
| size() | Возвращает количество элементов |
| clear() | Очищает список |
Критерии приёмки при выборе ArrayList
- Производительность доступа по индексу соответствует требованиям (O(1)).
- Частота вставок/удалений в середине низкая.
- Память: допустим небольшой рост внутреннего массива при добавлении.
- Блокировка/синхронизация: либо не требуется, либо предусмотрены внешние механизмы.
Ролевые чек-листы
Разработчик:
- Проверить, что операции по индексу критичны.
- Использовать типизированные коллекции (Generics).
- Обрабатывать возможные IndexOutOfBoundsException.
Архитектор:
- Оценить временную сложность операций.
- Решить, нужна ли потокобезопасность.
- Рассмотреть альтернативы для больших наборов данных.
Тестировщик:
- Тесты на добавление/удаление/многопоточный доступ.
- Проверить реакцию на null-элементы (ArrayList допускает null).
Примеры типичных ошибок и крайние случаи
- ConcurrentModificationException при модификации коллекции во время итерации через foreach.
- Использование ArrayList для огромных объёмов с частыми вставками в середину — плохая производительность.
- Ожидание фиксированной ёмкости: внутренний массив расширяется автоматические, но это может влиять на использование памяти.
Пример небольшого рабочего сценария
Полный пример: создание списка, добавление, поиск, удаление и итерация.
import java.util.*;
public class Example {
public static void main(String[] args) {
ArrayList alist = new ArrayList();
alist.add("apple");
alist.add("banana");
alist.add("orange");
System.out.println("Initial: " + alist);
// Вставка
alist.add(1, "kiwi");
System.out.println("After insert: " + alist);
// Поиск
int i = alist.indexOf("orange");
System.out.println("orange at: " + i);
// Удаление
alist.remove("banana");
System.out.println("After remove: " + alist);
// Итерация
for (String s : alist) {
System.out.println("Fruit: " + s);
}
}
} Быстрая проверка совместимости и миграции
- Код, использующий List интерфейс, легко переключается между ArrayList и LinkedList.
- Для многопоточных систем рассмотрите Collection.synchronizedList(new ArrayList<>()) или специализированные concurrent-классы.
Security и приватность
ArrayList сам по себе не добавляет новых рисков безопасности, но помните:
- Не храните чувствительные данные в коллекциях без шифрования/защиты.
- В многопоточном окружении синхронизируйте доступ при необходимости.
Полезные подсказки и рекомендации
- При массовом добавлении элементов заранее задайте initial capacity, чтобы уменьшить количество перераспределений.
- Используйте интерфейс List в сигнатурах методов — это повысит гибкость.
Заключение
ArrayList — базовый и полезный инструмент для большинства приложений, где важны упорядоченность и быстрый доступ по индексу. Осознанно выбирайте структуру данных: HashMap, LinkedList или очередь могут быть лучше в других сценариях.
Important: всегда анализируйте требования по производительности и многопоточности перед выбором реализации коллекции.
Краткое резюме:
- ArrayList хорош для случайного доступа и добавления в конец.
- Для частых вставок в середину используйте LinkedList.
- Для доступа по ключу используйте Map.
Похожие материалы
RDP: полный гид по настройке и безопасности
Android как клавиатура и трекпад для Windows
Советы и приёмы для работы с PDF
Calibration в Lightroom Classic: как и когда использовать
Отключить Siri Suggestions на iPhone