Каковы основные характеристики и сценарии использования ArrayList в Java?

«Каковы основные характеристики и сценарии использования ArrayList в Java?» — вопрос из категории Java Core, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

ArrayList — это реализация интерфейса List на основе динамически изменяемого массива. Это наиболее часто используемая коллекция в Java.

Ключевые характеристики:

  • Основа: Динамический массив (Object[]).
  • Доступ по индексу: O(1) — очень быстрый.
  • Вставка в конец: Амортизированная O(1), но может потребовать увеличения массива.
  • Вставка/удаление в середину: O(n) — требует сдвига элементов.
  • Порядок: Сохраняет порядок добавления элементов.
  • Дубликаты и null: Разрешает и то, и другое.

Базовые операции:

// Создание
List<String> list = new ArrayList<>();
List<Integer> sizedList = new ArrayList<>(100); // Начальная capacity

// Добавление
list.add("A"); // В конец
list.add(0, "B"); // По индекту (O(n) для середины!)

// Доступ
String element = list.get(2); // O(1)

// Удаление
list.remove(0); // По индекту (O(n) для середины!)
list.remove("A"); // По значению (O(n))

// Итерация
for (String s : list) { /* ... */ }
list.forEach(System.out::println);

Внутреннее устройство и производительность:

// При создании ArrayList() capacity по умолчанию = 10
// При добавлении элемента, если массив заполнен:
// 1. Создается новый массив размером ~ в 1.5 раза больше (oldCapacity + (oldCapacity >> 1))
// 2. Все элементы копируются в новый массив (System.arraycopy)
// Это операция O(n), но амортизированная стоимость добавления остается O(1)
Сравнение с LinkedList: Критерий ArrayList LinkedList
Структура Динамический массив Двусвязный список
Получение по индексу O(1) O(n)
Вставка в начало O(n) O(1)
Вставка в конец Амортизированная O(1) O(1)
Удаление из середины O(n) O(n) (но быстрее, если уже есть итератор)
Память Меньше (только данные + overhead массива) Больше (данные + 2 ссылки на узел)

Многопоточность:

  • ArrayList не синхронизирован. Для использования в многопоточных сценариях:

    // 1. Синхронизированная обертка
    List<String> syncList = Collections.synchronizedList(new ArrayList<>());
    
    // 2. CopyOnWriteArrayList (для read-heavy workloads)
    List<String> copyOnWriteList = new CopyOnWriteArrayList<>();

Рекомендации по использованию:

  1. Используйте ArrayList по умолчанию для списков, если не нужны специфические возможности других реализаций.
  2. Указывайте начальную capacity, если известен примерный размер списка, чтобы избежать лишних расширений массива.
  3. Используйте LinkedList только если нужны частые вставки/удаления в начало списка или работа через Iterator.
  4. Для частого поиска по значению используйте HashSet или HashMap.