JavaRush /Курси /Kotlin SELF /Керування циклом: break і continue

Керування циклом: break і continue

Kotlin SELF
Рівень 4 , Лекція 4
Відкрита

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, щоб цикл просто попросив ввести ще раз.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ