Java ArrayList: Constructors, add/addAll, set/get, and Iterator Mechanics
Java’s ArrayList offers a no‑arg constructor initializing an empty internal array, lazy default capacity of ten, and overloads for initial capacity or collection copying; its add/addAll methods ensure and grow capacity, set/get access elements, and its iterator tracks modCount to detect concurrent modifications.
ArrayList provides a no‑argument constructor that initializes an empty internal array with a default capacity of ten, represented by the constant DEFAULTCAPACITY_EMPTY_ELEMENTDATA . The source code shows the constructor and the declaration of the transient Object[] elementData field.
/** Constructs an empty list with an initial capacity of ten. */
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
transient Object[] elementData;
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};The no‑arg constructor actually creates an array of length zero; the default capacity of ten is applied lazily when the first element is added.
ArrayList also offers a constructor with an int initialCapacity parameter, which creates an Object[] of the given size, or uses a shared empty array when the capacity is zero. A negative capacity throws IllegalArgumentException .
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
}
}
private static final Object[] EMPTY_ELEMENTDATA = {};Another constructor accepts a Collection and copies its elements into the internal array.
public ArrayList(Collection
c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
this.elementData = EMPTY_ELEMENTDATA;
}
}The add(E e) method appends an element to the end of the list. It first ensures sufficient capacity by calling ensureCapacityInternal(size + 1) , which eventually invokes grow if the underlying array is too small.
public boolean add(E e) {
// ensure capacity
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}When the list is created with the no‑arg constructor, the first call to add triggers a growth from capacity 0 to the default capacity (10). Subsequent growth follows a 1.5× expansion strategy.
The overloaded add(int index, E element) inserts an element at a specific position. It checks the index, ensures capacity, shifts the tail with System.arraycopy , writes the new element, and increments size .
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1);
System.arraycopy(elementData, index, elementData, index + 1, size - index);
elementData[index] = element;
size++;
}addAll(Collection c) appends an entire collection. It converts the collection to an array, expands capacity, and copies the new elements with System.arraycopy . The indexed version works similarly but first shifts existing elements.
public boolean addAll(Collection
c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew);
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}The set(int index, E element) replaces the element at the given index and returns the previous value.
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}The get(int index) simply returns the element at the specified position after a bounds check.
public E get(int index) {
rangeCheck(index);
return elementData(index);
}Iteration is provided by the iterator() method, which returns an inner class Itr implementing Iterator . The iterator tracks modCount via expectedModCount to detect concurrent structural modifications.
public Iterator
iterator() {
return new Itr();
}
private class Itr implements Iterator
{
int cursor;
int lastRet = -1;
int expectedModCount = modCount;
public boolean hasNext() { return cursor != size; }
public E next() {
checkForComodification();
int i = cursor;
if (i >= size) throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}If the underlying list is modified directly (e.g., list.remove("Java") ) while an iterator is active, the mismatch between modCount and expectedModCount causes a ConcurrentModificationException . The correct way to remove elements during iteration is to use iterator.remove() , which updates both counters safely.
Java Tech Enthusiast
Sharing computer programming language knowledge, focusing on Java fundamentals, data structures, related tools, Spring Cloud, IntelliJ IDEA... Book giveaways, red‑packet rewards and other perks await!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.