1. Вступ
Коли ви починаєте використовувати enum у реальному коді, дуже швидко виникає цілком практичне питання: «Гаразд, у мене є ExpenseCategory.FOOD. Що з цим робити далі?» Показати гарну назву? Вибрати ліміт? Підібрати підказку для користувача? Вивести іконку текстом? І саме тут люди часто зʼїжджають у if або в «магічні рядки». when — це якраз інструмент, який перетворює обробку варіантів enum на зрозумілу таблицю відповідностей.
Уявіть, що enum — це набір кнопок на пульті. Кнопок небагато, вони підписані, а їхній список відомий заздалегідь. when — це ваша інструкція: «якщо натиснута така кнопка — роби ось це». А якщо згодом ви додасте нову кнопку, Kotlin зможе нагадати: «Гей, інструкцію для нової кнопки ви не написали».
2. Згадуємо when: оператор і вираз
На словах різниця звучить занудно: оператор — «просто виконує код», вираз — «повертає значення». Але на практиці саме це визначає, наскільки коротким і читабельним буде ваш код. Коли when використовується як вираз, ми часто позбуваємося тимчасових змінних і дублювання return. Код стає схожим на акуратну табличку: «ось вхід → ось вихід».
У Kotlin when можна писати і так, і так. І найприємніше: when-вираз можна присвоїти змінній або повернути з функції. Для enum це особливо природно: вхід — один із фіксованих варіантів, вихід — наприклад, текст для інтерфейсу або число для розрахунку.
Мініприклад: when як оператор
enum class Status { NEW, IN_PROGRESS, DONE }
fun printStatus(status: Status) {
when (status) {
Status.NEW -> println("Створено") // Створено
Status.IN_PROGRESS -> println("У роботі") // У роботі
Status.DONE -> println("Готово") // Готово
}
}
Тут when просто виконує println() у потрібній гілці.
Мініприклад: when як вираз
enum class Status { NEW, IN_PROGRESS, DONE }
fun statusLabel(status: Status): String =
when (status) {
Status.NEW -> "Створено"
Status.IN_PROGRESS -> "У роботі"
Status.DONE -> "Готово"
}
Зверніть увагу: гілки повертають рядки, і весь when також повертає рядок. Жодних var label = ... і трьох return.
3. Вичерпний when за enum
Зараз буде один із найприємніших моментів у Kotlin: коли when працює з enum, компілятор знає повний список варіантів. А отже, може перевірити, що ви обробили кожен із них. Це називається вичерпний when (exhaustive).
Психологічно це відчувається як «я не забув якийсь випадок». Технічно — як захист від майбутніх змін. Якщо ви додасте новий елемент enum, Kotlin підсвітить місця, де обробка стала неповною.
Зробімо невеликий фрагмент для нашого навчального консольного застосунку (умовно «облік витрат»). Нехай у нас є категорії витрат:
enum class ExpenseCategory { FOOD, TRANSPORT, HOME, FUN }
І ми хочемо гарно вивести назву категорії українською.
enum class ExpenseCategory { FOOD, TRANSPORT, HOME, FUN }
fun categoryTitle(category: ExpenseCategory): String =
when (category) {
ExpenseCategory.FOOD -> "Їжа"
ExpenseCategory.TRANSPORT -> "Транспорт"
ExpenseCategory.HOME -> "Дім"
ExpenseCategory.FUN -> "Розваги"
}
Тут немає else — і це добре. Чому? Бо якщо завтра ви додасте HEALTH, компілятор скаже: «Ага, функція categoryTitle() більше не покриває всі варіанти». І ви виправите це не «колись потім», а одразу.
Щоб краще відчути ідею, уявіть when за enum як сувору форму звіту: доки ви не заповнили всі рядки, звіт не приймають.
4. Чому else у when за enum часто шкідливий
Іноді рука тягнеться написати:
else -> "Невідомо"
Проблема в тому, що для enum «невідомо» найчастіше означає: «я додав новий варіант, а старий код мовчки проковтнув цю зміну й зробив щось дивне». Так, код компілюється. Але ціна — втрата автоматичної перевірки повноти.
Є ситуації, коли else усе-таки доречний. Наприклад, якщо ви свідомо не хочете розрізняти варіанти й задаєте типову поведінку. Але для логіки «таблиця відповідностей» майже завжди краще перелічити все явно.
Щоб порівняння було наочним, ось невелика таблиця:
| Підхід | Що виграємо | Що втрачаємо |
|---|---|---|
| when за enum без else | Компілятор перевіряє повноту, легше супроводжувати | Потрібно справді обробити всі варіанти |
| when за enum з else | Швидше «накинути», інколи зручно для прототипу | Компілятор перестає допомагати під час розширення enum |
5. Тип результату when-виразу
Коли when — це вираз, Kotlin має зрозуміти, який тип він повертає. Зазвичай усе просто: усі гілки повертають String — отже, when повертає String. Але інколи новачків дивує ситуація, коли гілки повертають різні типи, і компілятор починає «шукати спільний тип».
Для простоти запамʼятайте правило: усі гілки when-виразу мають повертати значення одного типу (або принаймні сумісного). Для enum це майже завжди природно: ви повертаєте або рядки, або числа, або якісь конкретні об’єкти.
Приклад: when повертає число
enum class ExpenseCategory { FOOD, TRANSPORT, HOME, FUN }
fun defaultLimit(category: ExpenseCategory): Int =
when (category) {
ExpenseCategory.FOOD -> 1500
ExpenseCategory.TRANSPORT -> 800
ExpenseCategory.HOME -> 2000
ExpenseCategory.FUN -> 1000
}
Приклад: спільний тип стає надто широким
enum class ExpenseCategory { FOOD, TRANSPORT }
fun weird(category: ExpenseCategory) =
when (category) {
ExpenseCategory.FOOD -> "їжа"
ExpenseCategory.TRANSPORT -> 123
}
Так можна написати, і Kotlin підбере спільний тип (найімовірніше Any). Але далі ви самі ускладните собі життя: замість зрозумілого String або Int ви отримаєте Any, і доведеться розбиратися, що саме там лежить. У реальному проєкті такий when майже завжди подає сигнал: «зупиніться, ви змішали дві різні задачі».
6. Nullable enum (E?): додаємо гілку null
У застосунках часто трапляється ситуація «значення ще не вибрано». Наприклад, категорію витрати не вказано, бо користувач увів лише суму. Тоді тип може бути ExpenseCategory?.
І тут важливе правило: якщо ви пишете when для nullable enum і хочете вичерпний when, потрібно обробити null окремою гілкою.
enum class ExpenseCategory { FOOD, TRANSPORT, HOME, FUN }
fun categoryTitleOrUnknown(category: ExpenseCategory?): String =
when (category) {
ExpenseCategory.FOOD -> "Їжа"
ExpenseCategory.TRANSPORT -> "Транспорт"
ExpenseCategory.HOME -> "Дім"
ExpenseCategory.FUN -> "Розваги"
null -> "Без категорії"
}
Виходить трохи довше, зате чесно. І читається дуже прозоро: «якщо null, значить без категорії».
7. Приклад: when за enum у консольному застосунку
Зараз зберемо мініфрагмент логіки, який справді часто трапляється в CLI-застосунках: команди користувача. Раніше ми могли порівнювати рядки ("add", "list", "exit"), але в рядках легко помилитися, та й рефакторити їх незручно. enum робить список команд явним.
Оголосимо enum команд
enum class Command { ADD, LIST, HELP, EXIT }
when як вираз: робимо підказку
enum class Command { ADD, LIST, HELP, EXIT }
fun commandHelp(command: Command): String =
when (command) {
Command.ADD -> "add — додати витрату"
Command.LIST -> "list — показати всі витрати"
Command.HELP -> "help — показати довідку"
Command.EXIT -> "exit — вийти з програми"
}
Це класичний випадок «таблиці відповідностей»: enum → текст.
when як оператор: виконуємо дію
Нехай поки дії будуть простими (ми не ускладнюємо застосунок, а лише показуємо стиль).
enum class Command { ADD, LIST, HELP, EXIT }
fun runCommand(command: Command) {
when (command) {
Command.ADD -> println("Додавання...") // Додавання...
Command.LIST -> println("Список...") // Список...
Command.HELP -> println("Довідка...") // Довідка...
Command.EXIT -> println("Вихід...") // Вихід...
}
}
Зауважте: це також вичерпно, і else не потрібен.
Як when-вираз прибирає дублювання
Типова проблема новачків — повторювати одне й те саме в кожній гілці. Наприклад, ви хочете виводити результат однаковим способом: println(result). Тоді зручно спочатку отримати рядок із when, а потім один раз надрукувати його.
enum class Command { ADD, LIST, HELP, EXIT }
fun runCommandMessage(command: Command): String =
when (command) {
Command.ADD -> "Гаразд, додаємо витрату"
Command.LIST -> "Показую список витрат"
Command.HELP -> "Ось довідка щодо команд"
Command.EXIT -> "Бувайте! (але код ще працює)"
}
fun main() {
val cmd = Command.HELP
println(runCommandMessage(cmd)) // Ось довідка щодо команд
}
Тут ви чітко розділили відповідальність: when обирає текст, а println робить виведення. До того ж таке легше «прокрутити в голові» і простіше тестувати.
Невелика схема: when за enum як контрольоване розгалуження
Іноді корисно побачити, що when за enum — це буквально розвилка з фіксованими дорогами. Ось блок-схема того, що робить when-вираз (на рівні ідеї):
flowchart TD
A[Отримали значення enum] --> B{"when(enum)"}
B -->|Варіант 1| C[Результат/дія #1]
B -->|Варіант 2| D[Результат/дія #2]
B -->|Варіант 3| E[Результат/дія #3]
B -->|Варіант N| F[Результат/дія #N]
Якщо enum має фіксований набір варіантів, компілятор може перевірити, що всі «дороги» існують. А якщо згодом ви додасте нову дорогу, компілятор вимагатиме додати й обробку — щоб користувач не «поїхав у кущі».
8. Типові помилки під час when з enum
Помилка №1: додавати else «про всяк випадок» і втрачати перевірку повноти.
Спочатку здається, що else — це страховка. Але для enum це часто перетворюється на пастку під час супроводу: ви розширили enum, а старий код продовжив компілюватися й поводитися не так, як задумано. Якщо ви справді хочете типову поведінку, краще явно назвати її: інколи це окремий enum-варіант (наприклад, UNKNOWN), а не мовчазний else.
Помилка №2: використовувати when як вираз, але повертати різні типи з гілок.
Kotlin підбере спільний тип, і ви раптово отримаєте Any, після чого почнуться дивні перевірки та приведення типів. У навчальних задачах це виглядає як «ну ж працює», а в реальному коді перетворюється на заплутаний контракт: незрозуміло, що саме повертає функція і як це використовувати.
Помилка №3: писати надто багато коду всередині гілок when.
when чудово читається, коли гілки короткі: повернути рядок, повернути число, викликати одну зрозумілу функцію. Коли всередині гілки зʼявляється пʼять println, цикл і ще один when, мозок починає просити відпустку. Хороший стиль: when обирає що зробити, а складна логіка живе в окремих функціях.
Помилка №4: забувати про null, коли enum nullable (E?).
Якщо ви перейшли на тип ExpenseCategory?, а в when не передбачили null, то або доведеться писати else, або ви отримаєте невичерпний when-вираз (і компілятор почне сваритися, якщо ви хочете повернути значення). У Kotlin null — це такий самий варіант, як і решта, просто він потребує окремої гілки.
Помилка №5: змішувати «вибір значення» і «виведення на екран» у кожній гілці.
Часто новачки роблять так: у кожній гілці when друкують щось, а потім ще раз друкують спільну частину. У результаті дублювання розростається. Зазвичай простіше зробити when виразом, отримати рядок, а потім один раз викликати println. Так код стає лінійним і передбачуваним.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ