Introduction: why did for-each become the "king" of loops in Java?
\n\nIf you've been writing Java for at least a week, you've definitely encountered the construct for (Type var : collection). This concise syntax is called the for-each loop (or enhanced for loop). It appeared back in Java 5 (2004) and quickly became a favorite among developers.
But do you know what happens under the hood when the JVM encounters this construct? Why can for-each be slower than a regular for loop for arrays? And why does it sometimes throw ConcurrentModificationException, even if you're not deleting anything?
In this article, we will analyze how for-each works in Java at the bytecode level, compare it with classic loops, and learn how to avoid typical pitfalls. Let's go!
\n\nSyntax and first acquaintance
\n\nThe basic syntax looks like this:
\n\nfor (ElementType variable : collection_or_array) {\n // loop body\n}\n\nExample with ArrayList:
List<String> names = Arrays.asList("Anna", "Boris", "Victor");\n\nfor (String name : names) {\n System.out.println("Hello, " + name + "!");\n}\n\nOutput:
\n\nHello, Anna!\nHello, Boris!\nHello, Victor!\n\nSimple, right? Yes. But behind this simplicity lie two completely different mechanisms — for arrays and for collections.
\n\nHow does for-each work with arrays?
\n\nWhen you write a for-each loop for an array, the Java compiler turns it into a regular index-based for loop. No magic.
Your code:
\n\nint[] numbers = {10, 20, 30};\n\nfor (int num : numbers) {\n System.out.println(num);\n}\n\nAfter compilation (decompile the bytecode — check for yourself):
\n\nint[] numbers = new int[]{10, 20, 30};\nfor (int i = 0; i < numbers.length; i++) {\n int num = numbers[i];\n System.out.println(num);\n}\n\nImportant conclusion: for arrays, for-each and the regular for loop are the same in terms of performance. No iterator overhead.
\n\nHow does for-each work with collections (List, Set)?
\n\nNow here's where it gets interesting. For collections, for-each uses an iterator.
\n\nYour code:
\n\nList<String> list = new ArrayList<>();\nlist.add("Java");\nlist.add("Python");\nlist.add("C++");\n\nfor (String lang : list) {\n System.out.println(lang);\n}\n\nThe compiler transforms this into:
\n\nList<String> list = new ArrayList<>();\nlist.add("Java");\nlist.add("Python");\nlist.add("C++");\n\nfor (Iterator<String> it = list.iterator(); it.hasNext(); ) {\n String lang = it.next();\n System.out.println(lang);\n}\n\nThat is:
\n- \n
list.iterator()is called — we get an iterator. \n - The condition is
it.hasNext(). \n - In the body —
it.next(). \n - The variable
langis initialized with the result ofnext(). \n
What does this mean in practice?
\n- \n
- You cannot remove elements inside a for-each loop using
list.remove()— you will get aConcurrentModificationException. Useit.remove()or a regular for loop. \n - You cannot modify the collection (add/remove) during iteration — same error. \n
- Performance: for
ArrayList, the difference from a regular for loop is minimal (creating an iterator object). ForLinkedList— for-each is much faster thanfor (int i = 0; i < list.size(); i++), becauseget(i)for LinkedList is O(n) each time! \n
What about Map? Iterating over key-value pairs
\n\nMap does not directly implement Iterable, so for-each