Einleitung: Warum for-each zum "König" der Schleifen in Java wurde?
\n\nWenn Sie auch nur eine Woche Java geschrieben haben, sind Sie bestimmt schon auf die Konstruktion for (Type var : collection) gestoßen. Diese prägnante Syntax wird als for-each-Schleife (oder enhanced for loop) bezeichnet. Sie wurde bereits in Java 5 (2004) eingeführt und schnell zum Liebling der Entwickler.
Aber wissen Sie, was unter der Haube passiert, wenn die JVM auf diese Konstruktion trifft? Warum kann for-each für Arrays langsamer sein als ein normales for? Und warum wirft es manchmal ConcurrentModificationException, selbst wenn Sie nichts löschen?
In diesem Artikel analysieren wir wie for-each in Java funktioniert auf Bytecode-Ebene, vergleichen es mit klassischen Schleifen und lernen, typische Fallstricke zu vermeiden. Los geht's!
\n\nSyntax und erste Bekanntschaft
\n\nDie grundlegende Syntax sieht so aus:
\n\nfor (ElementTyp variable : sammlung_oder_array) {\n // Schleifenkörper\n}\n\nEin Beispiel mit ArrayList:
List<String> names = Arrays.asList("Anna", "Boris", "Viktor");\n\nfor (String name : names) {\n System.out.println("Hallo, " + name + "!");\n}\n\nAusgabe:
\n\nHallo, Anna!\nHallo, Boris!\nHallo, Viktor!\n\nGanz einfach? Ja. Aber hinter dieser Einfachheit verbergen sich zwei völlig unterschiedliche Mechanismen – für Arrays und für Sammlungen.
\n\nWie funktioniert for-each mit Arrays?
\n\nWenn Sie for-each für ein Array schreiben, wandelt der Java-Compiler es in ein normales for mit Index um. Keine Magie.
Ihr Code:
\n\nint[] numbers = {10, 20, 30};\n\nfor (int num : numbers) {\n System.out.println(num);\n}\n\nNach der Kompilierung (dekompilieren Sie den Bytecode – überzeugen Sie sich selbst):
\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\nWichtige Schlussfolgerung: Für Arrays sind for-each und normales for in Bezug auf die Leistung identisch. Keine zusätzlichen Kosten für Iteratoren.
\n\nWie funktioniert for-each mit Sammlungen (List, Set)?
\n\nUnd hier wird es richtig interessant. Für Sammlungen verwendet for-each einen Iterator.
\n\nIhr 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\nDer Compiler wandelt dies um in:
\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\nDas heißt:
\n- \n
list.iterator()wird aufgerufen – wir erhalten einen Iterator. \n - In der Bedingung steht
it.hasNext(). \n - Im Schleifenkörper steht
it.next(). \n - Die Variable
langwird mit dem Ergebnis vonnext()initialisiert. \n
Was bedeutet das in der Praxis?
\n- \n
- Elemente können nicht gelöscht werden innerhalb von for-each über
list.remove()– Sie erhalten eineConcurrentModificationException. Verwenden Sieit.remove()oder ein normales for. \n - Die Sammlung darf nicht geändert werden (hinzufügen/löschen) während der Iteration – derselbe Fehler. \n
- Leistung: Für
ArrayListist der Unterschied zu einem normalen for minimal (Erstellung des Iterator-Objekts). FürLinkedListist for-each viel schneller alsfor (int i = 0; i < list.size(); i++), daget(i)bei LinkedList jedes Mal O(n) ist! \n
Was ist mit Map? Iteration über Schlüssel-Wert-Paare
\n\nMap implementiert Iterable nicht direkt, daher for-each