Why Your Java List Operations May Fail: Hidden Pitfalls and Fixes
This article reveals common pitfalls when using Java List implementations—such as Arrays.asList with primitive arrays, unsupported add/remove operations, side‑effects on the original array, subList memory leaks, LinkedList performance myths, and CopyOnWriteArrayList’s memory and iterator quirks—while providing practical solutions and best‑practice recommendations.
Introduction
We discuss several hidden pitfalls of List operations in real‑world Java development and how to solve them.
Arrays.asList with Primitive Arrays
Converting a primitive int[] with Arrays.asList yields a list of size 1 because the method receives the whole array as a single object ( List<int[]>).
int[] arr = {1, 2, 3};
List list = Arrays.asList(arr);
System.out.println(list.size()); // 1Solution 1: Use Arrays.stream(arr).boxed() (Java 8+).
List<Integer> collect = Arrays.stream(arr).boxed().collect(Collectors.toList());
System.out.println(collect.size()); // 3
System.out.println(collect.get(0).getClass()); // class java.lang.IntegerSolution 2: Declare the array with wrapper type.
Integer[] integerArr = {1, 2, 3};
List<Integer> integerList = Arrays.asList(integerArr);
System.out.println(integerList.size()); // 3Arrays.asList Returns an Unmodifiable List
The list returned by Arrays.asList cannot add or remove elements because it is an internal ArrayList class that extends AbstractList without implementing add or remove.
private static class ArrayList<E> extends AbstractList<E> implements RandomAccess, Serializable { ... }
public boolean add(E e) { throw new UnsupportedOperationException(); }
public E remove(int index) { throw new UnsupportedOperationException(); }Modifying the Original Array Affects the List
The internal ArrayList holds a reference to the original array, so any change to the array is reflected in the list.
int[] arr = {1,2,3};
List<int[]> list = Arrays.asList(arr);
arr[2] = 4; // list now sees the changeSolution: Wrap the result in a new ArrayList to break the reference.
List<Integer> list = new ArrayList<>(Arrays.asList(arr));subList Returns a View, Not a New List
subListproduces a view backed by the original list. Casting it to ArrayList causes ClassCastException because the actual class is ArrayList$SubList.
ArrayList strings = (ArrayList) names.subList(0, 1); // throws ClassCastExceptionModifying the sub‑list changes the original list, and modifying the original list while iterating the sub‑list throws ConcurrentModificationException. Use a new ArrayList to detach.
List<String> strings = new ArrayList<>(names.subList(0, 1));subList Can Cause OOM
Holding many sub‑list views prevents the large backing list from being garbage‑collected, leading to OutOfMemoryError.
for (int i = 0; i < 1000; i++) {
List<Integer> collect = IntStream.range(0, 100000).boxed().collect(Collectors.toList());
data.add(collect.subList(0, 1)); // strong reference keeps the 100k list alive
}Solution: Create a new list from the sub‑list or use skip / limit streams.
List<Integer> list = new ArrayList<>(collect.subList(0, 1));
// or
List<Integer> list = collect.stream().skip(0).limit(1).collect(Collectors.toList());LinkedList Insertion Is Not Always Faster
Benchmarks show that random insertions into a LinkedList are slower than into an ArrayList because each insertion requires node traversal.
StopWatch stopWatch = new StopWatch();
int elementCount = 100000;
// ArrayList insert benchmark
List<Integer> arrayList = IntStream.rangeClosed(1, elementCount).boxed().collect(Collectors.toCollection(ArrayList::new));
IntStream.rangeClosed(0, elementCount).forEach(i -> arrayList.add(ThreadLocalRandom.current().nextInt(elementCount), 1));
// LinkedList insert benchmark
List<Integer> linkedList = IntStream.rangeClosed(1, elementCount).boxed().collect(Collectors.toCollection(LinkedList::new));
IntStream.rangeClosed(0, elementCount).forEach(i -> linkedList.add(ThreadLocalRandom.current().nextInt(elementCount), 1));
System.out.println(stopWatch.prettyPrint());Conclusion: Use LinkedList only for head/tail operations after profiling.
CopyOnWriteArrayList Memory Overhead
Each write creates a new copy of the internal array, doubling memory usage and increasing GC pressure, especially with large lists.
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}CopyOnWriteArrayList Iterator Is Weakly Consistent
The iterator works on a snapshot of the array; modifications after iterator creation are invisible.
public Iterator<E> iterator() {
return new COWIterator<>(getArray(), 0);
}
static final class COWIterator<E> implements ListIterator<E> {
private final Object[] snapshot;
private int cursor;
// next(), hasNext() operate on snapshot only
}Demo shows that changes made in another thread are not reflected when iterating the original snapshot.
CopyOnWriteArrayList Iterator Does Not Support Modification
public void remove() { throw new UnsupportedOperationException(); }
public void set(E e) { throw new UnsupportedOperationException(); }
public void add(E e) { throw new UnsupportedOperationException(); }The iterator is read‑only.
Summary
Understanding the characteristics and hidden traps of Java collection classes—especially Arrays.asList, ArrayList, LinkedList, subList, and CopyOnWriteArrayList —helps developers choose the right container, avoid memory leaks, prevent unexpected exceptions, and achieve better performance.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Backend Technology
Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!
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.
