1. Введение
Когда вы впервые освоили while и for, может возникнуть ощущение: «Ну всё, теперь я могу повторять что угодно сколько угодно раз (и иногда даже случайно бесконечно)». Но в реальных программах почти всегда есть моменты, когда повторение нужно остановить раньше, или наоборот — пропустить конкретный шаг, потому что он «плохой» или «не подходит». И вот тут появляются break и continue: они помогают сделать код короче и честнее по смыслу, но при неосторожности превращают цикл в мини-лабиринт.
Kotlin поддерживает традиционные операторы break и continue для циклов. То есть это не «хак», не магия, а обычная часть языка, которую важно понимать так же уверенно, как if/else.
Представьте цикл как очередь в кофейне: итерации — это люди. Иногда кто-то говорит «я передумал» — и очередь заканчивается (это break). А иногда человек просто отходит «за кошельком» и пропускает обслуживание на этом шаге, но очередь продолжается (это continue). Важно только помнить: эти команды действуют внутри ближайшего цикла, и они не телепортируют выполнение «куда-нибудь», а в строго определённые места.
Небольшая схема того, что происходит в цикле, если у нас есть и continue, и break:
flowchart TD
A[Начало итерации] --> B{Условие цикла истинно?}
B -- нет --> Z[Выход из цикла]
B -- да --> C[Тело цикла]
C --> D{Нужно пропустить?}
D -- да --> E[continue]
E --> A
D -- нет --> F{Нужно выйти?}
F -- да --> G[break]
G --> Z
F -- нет --> A
2. break: как досрочно закончить цикл
break — это команда «выйти из цикла прямо сейчас». Она полезна, когда дальнейшие итерации не имеют смысла: вы уже нашли то, что искали, получили команду «выход», встретили «стоп-значение» или обнаружили состояние, в котором продолжать нельзя. Главное — понимать, что break прекращает текущий цикл и передаёт управление на строку после него. Это не «выход из программы», а именно выход из одного конкретного цикла.
В Kotlin break работает и в while, и в do-while, и в for. Он всегда один и тот же по эффекту: мгновенно завершает ближайший цикл. С точки зрения читаемости break чаще всего хорош, если точка выхода логически понятна: «ввод завершён», «найдено», «дальше бессмысленно». Если break раскидан по коду как конфетти, мозг читателя начинает грустить.
break в сценарии «ввод до сигнала окончания»
В консольных программах очень часто ввод делают «до сигнала»: пользователь вводит числа, а число 0 означает «завершить». Это как кнопка «Стоп» на микроволновке: не важно, сколько времени вы планировали греть, если вы уже достали тарелку.
fun main() {
var sum = 0
while (true) {
print("Введите число (0 — конец): ")
val n = readln().trim().toIntOrNull()
if (n == null) {
println("Это не число, попробуйте ещё раз.")
continue
}
if (n == 0) break
sum += n
}
println("Сумма = $sum") // например: Сумма = 17
}
Здесь мы используем стиль while (true) + break как вполне нормальный приём: условие завершения (ввод 0) удобнее проверять внутри, потому что оно зависит от того, что ввёл пользователь. Это читается честно: «крутимся бесконечно, пока не скажут остановиться». Но бесконечный цикл допустим только тогда, когда точка break очевидна и не спрятана в пяти уровнях условий.
break в for: «нашли — выходим»
Иногда for пробегает по диапазону, и нам нужно остановиться на первом подходящем значении. Например: найдём первое число от 1 до 50, которое делится на 7 и на 5 (то есть на 35). Можно, конечно, выписать формулу, но мы же тренируем циклы, а не пророческие способности.
fun main() {
var found = -1
for (x in 1..50) {
if (x % 7 == 0 && x % 5 == 0) {
found = x
break
}
}
println("Нашли: $found") // Нашли: 35
}
Обратите внимание на приём с found = -1. Это «значение по умолчанию», которое означает «не найдено». Мы пока не знаем null-safety как отдельную тему (это будет позже), поэтому для новичков такой способ часто проще: выбираем значение, которое в нормальном результате не встречается.
Почему break не должен быть спрятан
Очень частая проблема — написать break так, что его трудно заметить. Технически код будет работать, но читать его будет неприятно, как договор на 40 страниц мелким шрифтом.
Плохая привычка выглядит примерно так: внутри цикла много ветвлений, и break сидит где-то глубоко. Чтобы избежать этого, старайтесь ставить проверки выхода «поближе к началу итерации», особенно если они связаны с входными данными. Так мозгу проще: сначала проверили «мы продолжаем?», затем делаем полезную работу.
3. continue: как пропустить текущую итерацию
continue — это команда «закончить текущую итерацию прямо сейчас и перейти к следующей». То есть цикл не завершается, но оставшаяся часть тела не выполняется. Если представить итерацию как мини-сценарий, то continue — это «пропускаем эту серию, идём к следующей».
Kotlin, как и многие языки, поддерживает continue в циклах. На практике continue особенно полезен, когда вы хотите сделать «фильтр»: некоторые значения обрабатываем, некоторые — пропускаем. Это часто делает код короче, потому что убирает лишние вложенные if.
continue как фильтр в for
Допустим, мы хотим вывести только нечётные числа от 1 до 10. Можно написать условие «если нечётное — печатай», а можно перевернуть логику: «если чётное — пропусти».
fun main() {
for (i in 1..10) {
if (i % 2 == 0) continue
println(i) // 1 3 5 7 9 (каждое с новой строки)
}
}
Почему такая форма иногда удобнее? Потому что после continue вы можете писать «основную» логику без лишнего else. Это похоже на охранника на входе: «не подходишь — проходи мимо», а внутри клуба уже нормальная вечеринка (то есть основной код).
continue в while: осторожно, можно сделать бесконечный цикл
В for шаг происходит автоматически, а вот while держится на вашей дисциплине: вы обязаны менять состояние так, чтобы цикл когда-нибудь закончился. И именно тут continue может «выстрелить в ногу», потому что он легко перепрыгивает важные строки, включая обновление счётчика.
Посмотрите на пример: мы хотим вывести числа 1..5, но пропустить 3. Если написать наивно, можно случайно получить бесконечный цикл, который зависнет на 3.
Неправильный вариант (показываю как антипример, не делайте так):
fun main() {
var i = 1
while (i <= 5) {
if (i == 3) continue
println("i = $i")
i = i + 1
}
}
Что здесь произошло? На i == 3 срабатывает continue, а строка i = i + 1 пропускается. Значит i навсегда остаётся 3, и цикл никогда не закончится. Это тот самый момент, когда компьютер становится слишком буквальным другом: вы сказали «продолжай», он и продолжает… вечно.
Правильный вариант — обновлять i до continue (или так перестроить код, чтобы обновление было всегда):
fun main() {
var i = 1
while (i <= 5) {
if (i == 3) {
i = i + 1
continue
}
println("i = $i")
i = i + 1
}
}
Да, это выглядит чуть длиннее, но логика корректна: состояние, от которого зависит условие while, меняется всегда.
4. break и continue: вместе и без боли для читателя
Мини-сценарий «копилка чисел»
Сейчас соберём небольшой цельный сценарий, который выглядит как реальная консольная программа, а не набор отдельных фрагментов. Мы сделаем «копилку»: пользователь вводит числа (например, «сколько монет положили»), а программа накапливает сумму. Ввод завершится командным числом 0. Неправильные строки и отрицательные значения мы будем игнорировать, но не будем завершать программу — просто попросим ввести снова.
Здесь как раз естественно иметь continue для «плохого ввода» и break для «конца».
fun main() {
var total = 0
while (true) {
print("Введите сумму (целое >= 0, 0 — завершить): ")
val raw = readln().trim()
val value = raw.toIntOrNull()
if (value == null) {
println("Не похоже на целое число.")
continue
}
if (value < 0) {
println("Отрицательное не принимаем. Мы копим, а не грустим.")
continue
}
if (value == 0) break
total += value
println("Сейчас в копилке: $total") // например: Сейчас в копилке: 15
}
println("Готово. Итог: $total") // например: Готово. Итог: 15
}
Обратите внимание на важную деталь: порядок проверок здесь не случайный. Сначала мы разбираемся, число ли это вообще (toIntOrNull()), затем проверяем диапазон (не отрицательное ли), потом проверяем «стоп-значение» (0), и только после этого делаем полезную работу (прибавляем к total). Такой порядок делает код более предсказуемым, а значит — легче отлаживаемым.
Как держать код читаемым с break и continue
На этом этапе обучения есть один очень типичный соблазн: «О, я теперь могу прыгать по циклу! Давайте добавим ещё пару continue, ещё один break, и ещё один if, и получится… эээ… что получится». С break/continue важно помнить простое правило читаемости: если вы не можете быстро объяснить вслух, почему цикл может закончиться, и в каких случаях итерация пропускается, значит код уже пора упрощать.
Поскольку мы пока не используем пользовательские функции (это будет позже), упрощение обычно делается тремя способами. Во-первых, выносом промежуточных значений в понятные переменные (raw, value, isValid). Во-вторых, расположением «проверок-охранников» в начале итерации: всё, что ведёт к continue или break, стараемся поставить повыше. В-третьих, осторожным отношением к while (true): он хорош, когда смысл «цикл до команды выхода» читается явно, но плох, когда break спрятан далеко и не очевиден.
Есть ещё один нюанс, который пригодится уже в следующей лекции про вложенные циклы: break и continue без дополнительных средств относятся к ближайшему циклу. Сегодня мы глубоко в это не уходим, но важно хотя бы помнить, что «выход из двух циклов одним break» по умолчанию не случится — и это нормально.
5. Типичные ошибки при работе с break и continue
Ошибка №1: путать роли break и continue.
Новички иногда ожидают, что continue «выйдет из цикла», но он всего лишь пропускает текущую итерацию и переходит к следующей. В результате программа начинает «крутиться дальше», хотя человек хотел остановиться. Если вам нужно закончить цикл полностью — используйте break. Если нужно просто не выполнять оставшийся код итерации — используйте continue.
Ошибка №2: continue в while перепрыгивает обновление состояния.
Это классика бесконечных циклов: счётчик или другое состояние обновляется в конце тела, но на некоторых ветках стоит continue, который перескакивает обновление. Цикл «зависает» на одном и том же значении и не может дойти до условия завершения. Лечится дисциплиной: переменная, которая участвует в условии while, должна гарантированно меняться так, чтобы когда-нибудь условие стало false — даже на ветках с continue.
Ошибка №3: while (true) без явно читаемого break.
Бесконечный цикл сам по себе не зло, но он должен быть честным: точка выхода должна быть видна быстро, обычно в верхней части тела. Если break спрятан в глубине ветвлений, человек (и вы через два дня) перестаёт понимать, как цикл вообще заканчивается. В таких ситуациях лучше либо переписать условие в заголовок while (...), либо перестроить проверки так, чтобы break был очевиден.
Ошибка №4: слишком много «прыжков» в одном цикле.
Когда в цикле три continue и два break, код начинает напоминать игру «угадай, где ты окажешься через секунду». Формально это может быть правильно, но читать и поддерживать тяжело. Обычно помогает перестановка проверок в начале итерации и упрощение условий: меньше вложенности, больше прямого смысла.
Ошибка №5: использовать toInt() вместо toIntOrNull() в цикле ввода.
Если вы делаете ввод в цикле, пользователь почти гарантированно однажды введёт не то: пробел, пустую строку, «пять», «12.3», «-». toInt() в таком месте приводит к аварийному завершению программы, то есть ваш цикл даже не успеет «вежливо переспросить». Для устойчивого ввода используйте toIntOrNull(), а неверный ввод обрабатывайте через continue, чтобы цикл просто попросил ввести ещё раз.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ