Что происходит с элементами при увеличении внутреннего массива (capacity) в ArrayList?

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

Ответ

При необходимости увеличения вместимости (capacity) ArrayList создает новый массив большего размера и копирует все ссылки на элементы из старого массива в новый. Сами объекты-элементы при этом не дублируются.

Механизм (на примере Java 8+):

  1. Когда при добавлении элемента size == capacity, вызывается метод grow().
  2. Новая емкость рассчитывается как oldCapacity + (oldCapacity >> 1) (увеличение примерно в 1.5 раза).
  3. Создается новый массив: Object[] newElementData = new Object[newCapacity];
  4. Происходит поэлементное копирование ссылок с помощью нативного метода:
    System.arraycopy(oldElementData, 0, newElementData, 0, oldElementData.length);
  5. Внутренняя ссылка elementData начинает указывать на новый массив. Старый массив становится доступным для сборщика мусора.

Ключевые технические последствия:

  • Временная сложность операции: O(n), где n — текущее количество элементов. Это важно учитывать для производительности.
  • Копируются только ссылки: Объекты в куче (heap) остаются на своих местах. Изменение состояния объекта через одну ссылку будет видно через другую.

    ArrayList<StringBuilder> list = new ArrayList<>(2);
    list.add(new StringBuilder("A"));
    list.add(new StringBuilder("B"));
    // Произошло внутреннее расширение массива и копирование ссылок
    list.add(new StringBuilder("C"));
    
    StringBuilder firstElement = list.get(0);
    firstElement.append("_modified"); // Изменяем объект
    System.out.println(list.get(0)); // Вывод: "A_modified" — изменение видно
  • Стратегия роста (x1.5): Компромисс между экономией памяти (редкие расширения) и минимизацией избыточного копирования (слишком большие шаги роста).

Рекомендация: Если размер коллекции известен заранее, инициализируйте ArrayList с нужной начальной емкостью через конструктор new ArrayList<>(initialCapacity), чтобы избежать многократных операций копирования.