Как использовать break и continue внутри forEach в Kotlin?
Почему break и continue не работают в forEach?
Проблема в том, что forEach в Kotlin — это не управляющая конструкция, а функция-расширение, которая принимает лямбду. break и continue предназначены для прерывания или продолжения цикла на уровне байт-кода (например, for, while). Внутри лямбды break пытается выйти из функции, что невозможно, поэтому компилятор выдаёт ошибку.
Решение 1: Использовать return с меткой (label)
Самый идиоматичный способ — использовать return@forEach. Это аналог continue — он завершает текущую итерацию и переходит к следующей. Для break нужно использовать внешний run блок с меткой:
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
run outer@ {
numbers.forEach { number ->
if (number == 5) {
return@outer // аналог break
}
if (number % 2 == 0) {
return@forEach // аналог continue
}
println(number)
}
}
// Вывод: 1, 3
Объяснение:
return@forEach— завершает текущую итерацию (как continue).return@outer— выходит из внешнего блокаrun, что имитирует break.
Решение 2: Использовать обычный for или while
Если вам нужны классические break и continue без меток, просто используйте for:
for (number in numbers) {
if (number == 5) break
if (number % 2 == 0) continue
println(number)
}
// Вывод: 1, 3
Это самый простой и читаемый способ, когда логика требует прерывания цикла.
Решение 3: Использовать takeWhile/dropWhile для функционального стиля
Если вы предпочитаете функциональный подход без лямбда-меток, можно переписать логику через цепочку вызовов:
numbers
.takeWhile { it != 5 } // остановиться перед 5 (break)
.filter { it % 2 != 0 } // пропустить чётные (continue)
.forEach { println(it) }
// Вывод: 1, 3
Этот код эквивалентен break (через takeWhile) и continue (через filter). Минус — создаются промежуточные коллекции, что может быть неэффективно для больших списков.
Решение 4: Кастомная функция с прерыванием (для сложных случаев)
Можно написать свою функцию-расширение, которая принимает лямбду с предикатом остановки:
inline fun <T> Iterable<T>.forEachWithBreak(action: (T) -> Boolean) {
for (element in this) {
if (action(element)) break
}
}
// Использование:
numbers.forEachWithBreak { number ->
if (number == 5) return@forEachWithBreak true // break
if (number % 2 == 0) return@forEachWithBreak false // continue (не break)
println(number)
false // продолжить
}
Но этот вариант избыточен для простых случаев.
Вывод
Самый практичный совет:
- Для continue используйте
return@forEach. - Для break используйте
return@меткас внешним блоком или просто перепишите наfor. - Если логика чисто функциональная — рассмотрите
takeWhile/filter.
Альтернативный подход: использование Sequence для ленивых вычислений
Если вы работаете с большими коллекциями и хотите избежать создания промежуточных списков, преобразуйте коллекцию в Sequence с помощью asSequence(). Это позволит комбинировать функциональные операции без потери производительности:
numbers
.asSequence()
.takeWhile { it != 5 } // break
.filter { it % 2 != 0 } // continue
.forEach { println(it) }
// Вывод: 1, 3
Sequence обрабатывает элементы лениво — каждый элемент проходит через всю цепочку операций по одному. Это эффективно для больших коллекций и бесконечных последовательностей.
Когда это полезно?
- Когда коллекция очень большая (миллионы элементов) — не создаются промежуточные списки.
- Когда вам нужно остановиться рано (как в вашем примере) —
takeWhileпрекратит итерацию сразу после выполнения условия.
Этот подход более функциональный и соответствует идиомам Kotlin, но требует привыкания к ленивым последовательностям.