Safe List Operations: Remove, SubList Casting, and Efficient Traversal in Java
This article explains why modifying a List inside a foreach loop causes ConcurrentModificationException, shows the proper ways to remove elements using iterators or lambda expressions, warns against casting subList to ArrayList, clarifies correct toArray and Arrays.asList usage, compares LinkedList and ArrayList performance, and recommends the most efficient traversal techniques.
1. Do Not Modify a List Inside a foreach Loop
When you iterate a List with a foreach loop and try to remove or add elements, Java throws a ConcurrentModificationException . The foreach construct is implemented with an internal Iterator that records the collection’s modCount at creation. Any structural change that does not update the iterator’s expectedModCount triggers the exception.
Typical faulty code (illustrated in the image) results in this exception:
The underlying reason is that foreach uses an Iterator whose expectedModCount is set to the List’s modCount at the start of the loop. When remove() or add() changes modCount without updating expectedModCount, the mismatch causes the exception.
2. Correct Ways to Modify a List During Iteration
Lambda expression : Use list.removeIf(condition) or list.stream().filter(...).collect(...) for concise and safe removal.
Iterator : Explicitly obtain an Iterator and call iterator.remove() inside the loop. This is the approach recommended by Alibaba’s Java Development Manual .
Traditional for loop : Iterate by index and call list.remove(i) when needed, ensuring the index is adjusted after removal.
3. SubList Casting Pitfall
Calling list.subList(...) returns an internal SubList view, not an actual ArrayList. Casting this view to ArrayList triggers a ClassCastException :
java.lang.ClassCastException: class java.util.ArrayList$SubList cannot be cast to class java.util.ArrayListSolution: Create a new ArrayList from the sublist, e.g., new ArrayList<>(list.subList(...)), which copies the elements into a genuine ArrayList.
4. Proper Use of toArray
When converting a List to an array, always use the generic form list.toArray(new T[0]) (or new T[list.size()]). The method reallocates a new array if the supplied one is too small, and sets the element at index list.size() to null when the array is larger.
5. Arrays.asList Is Read‑Only
The list returned by Arrays.asList() is a fixed‑size view backed by the original array. Calls to add, remove, or clear throw UnsupportedOperationException because the underlying array cannot change size.
6. When Is LinkedList Faster Than ArrayList?
Although a LinkedList’s node‑based structure makes insertions and deletions in the middle faster than shifting elements in an ArrayList, real‑world benchmarks show that the advantage only appears in specific scenarios:
Inserting into the middle of a large list.
Removing elements (which also avoids array copying).
When an ArrayList must repeatedly resize its internal array.
For random access and queries, ArrayList is consistently faster because it uses direct index lookup, while LinkedList requires traversal.
7. Most Efficient List Traversal Techniques
Four common traversal styles are presented:
Traditional for loop and enhanced foreach loop.
Explicit Iterator.
Lambda expression (e.g., list.forEach(item -> ...)).
Stream API (e.g., list.stream().forEach(...)).
For readability and performance, the article recommends using the Stream API (with caution on parallel streams) or Lambda expressions, and always performing a null‑check on the List before iteration to avoid NullPointerException.
Ma Wei Says
Follow me! Discussing software architecture and development, AIGC and AI Agents... Sometimes sharing insights on IT professionals' life experiences.
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.
