Warum break und continue in Kotlin forEach nicht funktionieren (und wie man es behebt)
\n\nWenn Sie jemals in Kotlin programmiert und versucht haben, eine forEach-Schleife vorzeitig mit break zu verlassen, sind Sie höchstwahrscheinlich auf eine rote Unterstreichung in der IDE und einen Kompilierungsfehler gestoßen. Kommt Ihnen bekannt vor? Sie sind nicht allein – dies ist eine der häufigsten Fragen zu Kotlin auf Stack Overflow mit tausenden von Aufrufen. In diesem Artikel zeigen wir nicht nur, wie Sie die Einschränkung umgehen, sondern werfen auch einen Blick unter die Haube: Warum wurde es so gemacht, wie funktioniert return innerhalb einer Lambda und welche Alternativen gibt es? Los geht's!
Klassisches Beispiel: Anfängerfehler
\n\nStellen Sie sich vor, Sie haben eine Liste von Zahlen und möchten nur die ersten drei Elemente auf dem Bildschirm ausgeben. Intuitiv möchte man so schreiben:
\n\nval numbers = listOf(1, 2, 3, 4, 5)\nnumbers.forEach {\n println(it)\n if (it == 3) break // Kompilierungsfehler!\n}\n\n\nDer Compiler gibt einen Fehler aus: ‘break’ and ‘continue’ are only allowed inside a loop. Warum? Schließlich ist forEach doch eine Art Schleife? Die Antwort liegt darin, wie forEach tatsächlich aufgebaut ist.
Anatomie von forEach: Es ist keine Schleife, sondern ein Funktionsaufruf
\n\nÖffnen wir die Standardbibliothek von Kotlin und betrachten die Signatur von forEach:
public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {\n for (element in this) action(element)\n}\n\n\nDas Schlüsselwort hier ist inline. Das bedeutet, dass der Funktionskörper während der Kompilierung direkt an die Aufrufstelle eingefügt wird. Trotzdem ist forEach eine Funktion höherer Ordnung, die eine andere Funktion (Lambda) als Argument akzeptiert. Eine Lambda ist jedoch kein Codeblock einer Schleife, sondern ein gewöhnliches Funktionsobjekt. break und continue sind nur innerhalb von for-, while- oder do-while-Konstrukten sinnvoll, nicht innerhalb einer beliebigen Funktion.
Mit anderen Worten: Wenn Sie numbers.forEach { ... } schreiben, erzeugen Sie keine Schleife – Sie rufen eine Methode auf, die selbst intern eine for-Schleife verwendet. Der Compiler kann break nicht aus Ihrer Lambda in die äußere Schleife „durchreichen“, da die Lambda eine separate Entität ist. Das ist kein Fehler, sondern Design.
Wie return in forEach funktioniert (überraschende Wendung)
\n\nreturn innerhalb von forEach funktioniert jedoch ... aber nicht so, wie Sie erwarten. Schauen Sie:
fun main() {\n val numbers = listOf(1, 2, 3, 4, 5)\n numbers.forEach {\n if (it == 3) return // Beendet main()!\n println(it)\n }\n println("Diese Zeile wird nicht ausgeführt")\n}\n\n\nDie Ausgabe ist: 1 2. return ist hier ein non-local return (nicht-lokale Rückkehr). Es beendet nicht nur die Lambda, sondern die gesamte äußere Funktion (main). Dies funktioniert, weil forEach als inline deklariert ist. Der Compiler fügt den Lambda-Code direkt in den Körper von main ein, und return bezieht sich dann auf die nächstgelegene deklarierte fun, also main.
Wenn Sie nur aus forEach aussteigen möchten, nicht aus der gesamten Funktion, verwenden Sie ein Label (Markierung):
fun main() {\n val numbers = listOf(1, 2, 3, 4, 5)\n numbers.forEach loop@{\n if (it == 3) return@loop\n println(it)\n }\n println("Diese Zeile wird ausgeführt")\n}\n\n\nAusgabe: 1 2 und Diese Zeile wird ausgeführt. Das Label loop@ sagt dem Compiler: „Verlasse die Lambda, aber nicht main“. Im Grunde ist dies ein Analogon zu continue – wir überspringen die aktuelle Iteration und gehen zum nächsten Element über.