List, ArrayList, LinkedList & iteration
List operations, ArrayList vs LinkedList trade-offs, Iterator/ListIterator, fail-fast iteration, and the defensive-copy fix for ConcurrentModificationException.
List: the ordered, indexed, duplicate-allowing collection
List<E> adds index-based operations to Collection: add(e), add(i, e), get(i), set(i, e), remove(i), indexOf(e), size(). Two main implementations make different performance trade-offs:
- ArrayList — backed by a resizable array. get(i)/set(i) are O(1); appending is amortised O(1); inserting or removing in the middle is O(n) (it shifts elements). This is the default choice.
- LinkedList — a doubly-linked list. get(i) is O(n) (it walks the chain), but inserting/removing at a known position (especially the ends) is O(1). It also implements Deque, so it doubles as a queue/stack.
Rule of thumb: random access and iteration → ArrayList; heavy add/remove at the ends → LinkedList or ArrayDeque.
### Three ways to iterate
- for-each (
for (E e : list)) — cleanest; use when you don't need the index and won't modify during the loop. Iterator—it.hasNext()/it.next(), withit.remove()to delete the last-returned element safely during iteration.ListIterator— bidirectional (hasPrevious/previous), cansetandaddduring traversal.
### Fail-fast and ConcurrentModificationException
Collection iterators are fail-fast: if the underlying collection is *structurally modified* (an add or remove that changes its size) while you are iterating it through anything other than the iterator's own remove, the iterator detects the change and throws ConcurrentModificationException (CME) on the next next(). This is a safety feature — it surfaces a bug instead of silently corrupting the traversal.
for (Event e : eventList)
if (e.isDone())
eventList.remove(e); // BUG: modifies the list mid-iteration → CME
Worked example — the Greenhouse fix. The project's Controller.run() needs to fire ready events and remove them from the list during the same loop. It avoids the CME by iterating a defensive copy while mutating the original:
public void run() {
while (eventList.size() > 0)
// iterate a fresh copy, mutate the real list:
for (Event e : new ArrayList<Event>(eventList))
if (e.ready()) {
System.out.println(e);
e.action();
eventList.remove(e); // safe: removing from eventList, not the copy
}
}
The loop walks new ArrayList<Event>(eventList) — a snapshot that is never modified — while eventList.remove(e) shrinks the *original*. No iterator over eventList is active, so no CME. The other correct fixes for the simpler delete-while-looping case are Iterator.remove() or list.removeIf(Event::isDone).