Как использовать break и continue внутри forEach в Kotlin?
Проблема: почему break и continue не работают в forEach?
forEach — это не конструкция цикла, а inline-функция, принимающая лямбду. break и continue работают только с реальными циклами (for, while, do-while). Внутри лямбды return по умолчанию выходит из ближайшей fun-функции (не лямбды), что обычно приводит к выходу из всей внешней функции.
Решение 1: Использование return с меткой (label)
Самый простой способ — использовать return@forEach. Это аналог continue — он завершает текущую итерацию и переходит к следующей:
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
numbers.forEach { i ->
if (i % 2 == 0) return@forEach // аналог continue
println(i)
}
// Вывод: 1, 3, 5, 7, 9
Для аналога break (полный выход из forEach) можно использовать внешнюю метку или более явное решение:
run loop@ {
numbers.forEach { i ->
if (i == 5) return@loop // аналог break — выходим из run
if (i % 2 == 0) return@forEach // аналог continue
println(i)
}
}
// Вывод: 1, 3
Решение 2: Использование takeWhile или обычного цикла
Если вам нужно именно прервать выполнение при условии, лучше использовать takeWhile или вернуться к обычному for:
// Аналог break через takeWhile
numbers.takeWhile { it != 5 }
.forEach { i ->
if (i % 2 == 0) return@forEach
println(i)
}
// Вывод: 1, 3
Или самый прямой способ — просто использовать for:
for (i in numbers) {
if (i == 5) break
if (i % 2 == 0) continue
println(i)
}
// Вывод: 1, 3
Решение 3: Кастомная функция с ранним выходом
Вы можете написать свою функцию, которая принимает лямбду и поддерживает ранний выход через исключение или флаг:
inline fun <T> Iterable<T>.forEachBreakable(action: (T) -> Unit) {
for (item in this) {
action(item)
}
}
// Использование (не рекомендуется, лучше придерживаться стандартных подходов)
numbers.forEachBreakable { i ->
if (i == 5) return@forEachBreakable // аналог break — выходим из forEachBreakable
if (i % 2 == 0) return@forEachBreakable // аналог continue
println(i)
}
Важные замечания
- return@forEach — это аналог
continueдляforEach. Он работает как обычный return с меткой, указывая, что нужно выйти из лямбды (итерации), а не из внешней функции. - Для
breakиспользуйте внешние блоки (run,with) с метками или переходите на обычные циклы. - Если вам часто нужно прерывать итерацию, возможно,
forEach— не лучший выбор. Рассмотритеfor,whileили функции-расширения вродеtakeWhile,first,find. - В Kotlin
breakиcontinueне поддерживаются в лямбдах по дизайну — это сознательное решение для упрощения семантики и избежания путаницы.
Альтернативный подход: использование Sequence и yield
Если вы работаете с большими коллекциями или сложными цепочками операций, можно использовать Sequence с ленивыми вычислениями. Это даёт больше контроля над потоком выполнения:
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val result = sequence {
for (i in numbers) {
if (i == 5) break // break работает внутри sequence
if (i % 2 == 0) continue // continue тоже работает
yield(i) // добавляем элемент в последовательность
}
}.toList()
println(result) // Вывод: [1, 3]
Преимущества:
- Вы можете использовать
breakиcontinueв привычном виде. - Последовательности ленивы — элементы вычисляются только по мере необходимости.
- Легко комбинировать с другими операторами (map, filter, etc).
Недостатки:
- Немного многословнее, чем простой
forEach. - Требуется понимание концепции последовательностей.
Этот подход особенно полезен, когда вам нужно не только прервать цикл, но и собрать результаты в новую коллекцию или выполнить дополнительные преобразования.