Ответ
При необходимости увеличения вместимости (capacity) ArrayList создает новый массив большего размера и копирует все ссылки на элементы из старого массива в новый. Сами объекты-элементы при этом не дублируются.
Механизм (на примере Java 8+):
- Когда при добавлении элемента
size == capacity, вызывается методgrow(). - Новая емкость рассчитывается как
oldCapacity + (oldCapacity >> 1)(увеличение примерно в 1.5 раза). - Создается новый массив:
Object[] newElementData = new Object[newCapacity]; - Происходит поэлементное копирование ссылок с помощью нативного метода:
System.arraycopy(oldElementData, 0, newElementData, 0, oldElementData.length); - Внутренняя ссылка
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), чтобы избежать многократных операций копирования.