1. Навіщо потрібен when замість довгих if/else if
Майже будь-який застосунок, який спілкується з людиною (або з іншим застосунком), рано чи пізно починає обирати поведінку за «кодом»: числом, рядком або символом. Спочатку все виглядає нормально: два варіанти — два if. Але щойно варіантів стає 4–6, ланцюжок else if витягується вниз, як чек із супермаркету, де ви купили «щось за акцією». А потім раптом усвідомили, що це був ополоник за 69 євро.
Проблема не в тому, що if/else if — поганий інструмент. Він чудовий. Проблема в іншому: довгий ланцюжок читається гірше. Оку складніше одразу побачити «ось список усіх варіантів». А ще важче — швидко зрозуміти, що станеться, якщо значення не підходить під жоден із них. Kotlin якраз дає більш «табличну» форму такого розгалуження — when.
Почнімо з дуже типової ситуації: є значення, і ви хочете виконати різні дії для різних варіантів.
fun main() {
val role = "editor"
if (role == "viewer") {
println("Доступ: лише читання")
} else if (role == "editor") {
println("Доступ: редагування")
} else {
println("Невідома роль: $role")
}
}
Код працює. Але якщо ролей стане шість, усе це виглядатиме вже не так дружньо. Саме тут і зʼявляється when.
Ідея when: «таблиця відповідностей» замість «драбини умов»
when (value) зручно читати так: «у мене є значення value; якщо воно дорівнює ось цьому — роби те; якщо дорівнює іншому — роби інакше; у протилежному разі — виконай запасний варіант». За змістом це схоже на switch з інших мов, але в Kotlin синтаксис зазвичай охайніший і передбачуваніший.
Тобто замість «драбини» if/else if/else ви отримуєте конструкцію, яка візуально схожа на список варіантів. Це особливо зручно, коли ви пишете меню, обробляєте коди помилок, режими роботи, команди та інші «перемикачі» поведінки.
Перепишімо приклад із роллю через when:
fun main() {
val role = "editor"
when (role) {
"viewer" -> println("Доступ: лише читання")
"editor" -> println("Доступ: редагування")
else -> println("Невідома роль: $role")
}
}
Тут одразу видно і «які варіанти підтримуються», і «що буде інакше».
2. Синтаксис when (value) і правила читання
Базовий синтаксис when (value) { ... }
Синтаксис when виглядає так: спочатку пишете when (value), потім — фігурні дужки, а всередині — гілки. Кожна гілка в базовому вигляді читається як «значення -> дія».
Важливо звикнути до трьох елементів:
- value — це те, що ви порівнюєте (наприклад, номер пункту меню або рядок-режим).
- Ліворуч від -> — «із чим порівнюємо».
- Праворуч від -> — що виконати, якщо значення збіглося.
Мінішаблон (майже як шпаргалка, але легальна):
fun main() {
val x = 2
when (x) {
1 -> println("Один") // Один
2 -> println("Два") // Два
else -> println("Інше")
}
}
Виглядає просто, але за цією простотою є важлива логіка: перевірки йдуть зверху вниз, і виконується перша відповідна гілка.
else — гілка «про всяк випадок», яка робить застосунок дорослішим
Коли людина вводить дані, вона іноді вводить не те. Іноді — спеціально. Іноді — випадково. А іноді кіт пройшовся по клавіатурі: це теж користувацьке введення, просто в «котячому режимі». Тому гілка else у when — це страховка: «усі інші значення, які я не перелічив».
Водночас є важливий нюанс. Якщо when використовується як оператор (тобто просто виконує дії), Kotlin не вимагає обробити всі варіанти. У такому разі, якщо значення не підійде під жодну гілку, просто нічого не виконається.
Однак у навчальних і прикладних консольних застосунках майже завжди краще явно писати else, щоб користувач отримав зрозумілу відповідь.
Приклад коректної обробки невідомого значення:
fun main() {
val day = 9
when (day) {
1 -> println("Пн") // не виконається
2 -> println("Вт") // не виконається
3 -> println("Ср") // не виконається
else -> println("Невідомий день: $day") // Невідомий день: 9
}
}
else майже завжди логічніше ставити наприкінці. Тоді «таблиця варіантів» читається зверху вниз, а «інакше» справді означає «інакше».
Гілка як блок: коли в одній гілці потрібно кілька дій
Часто здається, що гілка when — це обовʼязково «одна дія в один рядок». Насправді в гілці можна виконувати кілька дій: просто замість одного виразу праворуч від -> ставиться блок { ... }.
Це особливо корисно, коли ви хочете, наприклад, вивести два повідомлення або спочатку щось порахувати, а потім надрукувати результат. Поки ви використовуєте when як оператор (тобто «робите дії»), це виглядає дуже природно.
Приклад із «багаторядковою відповіддю»:
fun main() {
val code = 400
when (code) {
200 -> println("OK") // не виконається
400 -> {
println("Bad Request") // Bad Request
println("Перевірте запит") // Перевірте запит
}
else -> println("Інший код: $code")
}
}
Зверніть увагу: фігурні дужки всередині гілки — це не «дужки when», а саме «дужки гілки». Тобто це маленьке тіло, у якому можна писати кілька рядків.
Як обирається гілка: порядок зверху вниз і «перша, що перемогла»
Дуже важливо розуміти механіку вибору гілки. Інакше можна випадково написати код, який виглядає логічно, але працює «не так». when порівнює значення з гілками за порядком, зверху вниз. Щойно знаходить збіг — виконує цю гілку й виходить.
У цій лекції ми не робитимемо гілки з діапазонами й умовами (це окрема тема). Але навіть у простому порівнянні «за рівністю» корисно памʼятати: порядок гілок — це частина логіки.
Схема виконання виглядає приблизно так:
flowchart TD
A["Є значення x"] --> B["Порівняти з першою гілкою"]
B -->|збіглося| C["Виконати дії гілки 1 і вийти"]
B -->|не збіглося| D["Порівняти з другою гілкою"]
D -->|збіглося| E["Виконати дії гілки 2 і вийти"]
D -->|не збіглося| F["..."]
F --> G["Якщо нічого не збіглося → else (якщо є)"]
Звідси випливає просте правило стилю: else тримаємо внизу, а варіанти перелічуємо зверху вниз так, як ви хочете, щоб їх читали люди.
3. Практика: меню «Гаманець» і перехід на when
Зараз зробимо практичний фрагмент, який добре показує користь when: обробку вибору пункту меню. Ми напишемо маленький «гаманець» — застосунок, який зберігає поточний баланс (просто число) і пропонує користувачу обрати дію. Ми не використовуємо колекції та складні структури даних. Наша мета — навчитися перемикати поведінку за значенням, а не будувати банк.
Друкуємо меню й читаємо вибір
fun main() {
println("=== Гаманець ===")
println("1 — показати баланс")
println("0 — вихід")
print("Ваш вибір: ")
val choice = readln().trim()
println("Ви обрали: $choice") // наприклад: Ви обрали: 1
}
Поки що вибір — це рядок. Це нормально: readln() завжди читає рядок, а ви підготовлюєте його через trim() (прибираєте зайві пробіли).
Перетворюємо введення на число безпечно
Ви вже вмієте так робити: toIntOrNull() повертає Int?, тобто або число, або null, якщо користувач увів не число.
fun main() {
print("Ваш вибір: ")
val choice = readln().trim().toIntOrNull()
if (choice == null) {
println("Потрібно ввести число.") // Потрібно ввести число.
return
}
println("Ви ввели номер: $choice")
}
Тут ви використовуєте «ранній вихід» (return), щоб далі працювати лише з коректними даними.
Та сама логіка через if/else if, щоб було з чим порівняти
Нехай у нас є баланс.
fun main() {
var balance = 100
print("Ваш вибір (1/0): ")
val choice = readln().trim().toIntOrNull() ?: return
if (choice == 1) {
println("Баланс: $balance") // Баланс: 100
} else if (choice == 0) {
println("Вихід") // Вихід
} else {
println("Невідома команда: $choice")
}
}
Працює, але вже видно, що це «драбинка».
Переписуємо на when (choice)
Тепер зробимо те саме через when. По суті, ви перетворюєте розгалуження на «таблицю»: «1 → показати баланс», «0 → вихід», «інакше → помилка».
fun main() {
var balance = 100
print("Ваш вибір (1/0): ")
val choice = readln().trim().toIntOrNull() ?: return
when (choice) {
1 -> println("Баланс: $balance") // Баланс: 100
0 -> println("Вихід") // Вихід
else -> println("Невідома команда: $choice")
}
}
З погляду поведінки застосунок не став «розумнішим». Зате з погляду читання коду він став спокійнішим: менше візуального шуму й більше відчуття «списку варіантів».
Додаємо дії, щоб відчути масштабованість
Додаймо пункти «поповнити» і «витратити». Самі операції зробимо максимально простими: читаємо число й змінюємо баланс. Тут уже when починає виглядати особливо доречно.
fun main() {
var balance = 100
println("1 — баланс, 2 — поповнити, 3 — витратити, 0 — вихід")
print("Ваш вибір: ")
val choice = readln().trim().toIntOrNull() ?: return
when (choice) {
1 -> println("Баланс: $balance") // Баланс: 100
2 -> { print("Сума: "); balance += readln().trim().toInt() }
3 -> { print("Сума: "); balance -= readln().trim().toInt() }
0 -> println("Вихід")
else -> println("Невідома команда: $choice")
}
}
Зауважте: гілки 2 і 3 ми зробили блоками { ... }, бо там дві дії — запитати суму й змінити баланс. Такий прийом із блоком у гілці — нормальна частина when.
Так, тут можна поліпшувати безпеку (наприклад, захищатися від нечислової суми), але ми робитимемо це поступово. Сьогодні наша мета — навчитися обирати поведінку за значенням красиво й читабельно.
Робимо меню «справжнім»: цикл + when
Меню зазвичай не одноразове: користувач обирає дію кілька разів. У Kotlin є while, тож зберімо просту версію: «крутиться, доки не вийдете». Усередині буде when, який маршрутизує дії.
fun main() {
var balance = 100
var running = true
while (running) {
print("1-баланс 2+ 3- 0-вихід: ")
val choice = readln().trim().toIntOrNull() ?: continue
when (choice) {
1 -> println("Баланс: $balance")
2 -> { print("Сума: "); balance += readln().trim().toInt() }
3 -> { print("Сума: "); balance -= readln().trim().toInt() }
0 -> { println("Бувайте!"); running = false } // Бувайте!
else -> println("Невідомо: $choice")
}
}
}
Зверніть увагу на акуратну звʼязку: якщо choice не вдалося перетворити на число, ви робите continue і просто повторюєте введення. Це не тема when, але типовий «скелет» консольних застосунків — цикл + читання + розгалуження.
4. Типові помилки під час роботи з when (value)
На початку when зазвичай здається «надто простим, щоб помилятися», але помилки все одно трапляються — здебільшого через читабельність і звички після if/else. Добра новина: ці помилки рідко бувають складними. І майже завжди їх видно одразу, якщо навчитися читати when як таблицю варіантів, а не як «ще один if».
Помилка №1: забувають про else і отримують «мовчазну поведінку».
Технічно when як оператор може не бути вичерпним: якщо жодна гілка не збігнеться, нічого не виконається. Але в навчальних задачах і CLI‑застосунках це зазвичай поганий UX: користувач уводить «9», а застосунок ніби завис (хоча насправді він просто нічого не зробив). Найчастіше краще явно писати else і виводити зрозуміле повідомлення.
Помилка №2: ставлять else не останнім і ховають сенс гілок нижче.
Логіка when — зверху вниз: перша відповідна гілка «перемагає». Якщо поставити else раніше, усе, що нижче, стане недосяжним або беззмістовним. Навіть коли компілятор за це не «сварить», читач коду починає нервувати: «навіщо там гілки, якщо є else вище?». Тримайте else наприкінці — як правило стилю.
Помилка №3: роздувають гілки до 20–30 рядків і перетворюють when на «міні‑застосунок».
when хороший як диспетчер: обрати варіант і запустити потрібну дію. Коли всередині гілки зʼявляється половина застосунку, «таблиця варіантів» перестає бути таблицею й перетворюється на довге полотно. У таких випадках допомагає дисципліна: гілка коротка, а подробиці переїжджають в окрему функцію (ви вже вмієте їх писати).
Помилка №4: намагаються замінити if там, де всього два варіанти.
За конвенціями Kotlin зазвичай надають перевагу if для бінарного вибору, а when — коли варіантів три й більше. Це не «закон», але добрий орієнтир: якщо у вас «так/ні», то if часто читається простіше, ніж when заради однієї перевірки та else.
Помилка №5: порівнюють «брудне введення» як є й дивуються, що гілки не спрацьовують.
Сьогодні ми здебільшого порівнювали числа, але навіть там є «бруд»: пробіли, порожній рядок, нечислові символи. Якщо значення приходить рядком, то без нормалізації (trim(), іноді lowercase()) легко отримати ситуацію «користувач увів правильно, але застосунок думає інакше». Звичка «спочатку підготуйте введення, потім розгалужуйтеся» економить години налагодження й трохи зберігає віру в людство.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ