JavaRush /Курси /Kotlin SELF /when як вираз

when як вираз

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

1. Навіщо використовувати when як вираз

Коли ви пишете консольні програми, дуже легко скотитися до стилю «у кожній гілці println(...)». Це працює… доки програма невелика. Але щойно вам потрібно буде спочатку обчислити текст повідомлення, потім вирішити, куди його надіслати (у консоль, у лог, у файл) або повернути результат із функції, одразу знадобиться when, який повертає значення, а не просто виконує дію.

У Kotlin when може бути і оператором (statement), і виразом (expression). Різниця проста: вираз має результат — його можна присвоїти змінній або повернути з функції. Kotlin прямо підкреслює цю ідею: when як вираз повертає значення, а як оператор — лише виконує код, не створюючи результату.

Мініпорівняння: оператор vs вираз

Чого ми хочемо Як пишемо Що отримуємо
«Зроби дію»
when (x) { ... -> println(...) }
Нічого не повертаємо — лише виконуємо
«Поверни значення»
val text = when (x) { ... -> "..." }
Отримуємо значення й використовуємо його далі

Саме так цю ідею описано в офіційному поясненні when як expression vs statement.

2. Базовий синтаксис: val result = when (...) { ... }

Зараз зробимо найважливіший крок на сьогодні: перестанемо друкувати всередині when (хоча інколи це доречно) і почнемо отримувати рядок або число як результат. Це схоже на if як вираз, який ви вже бачили: замість «розгалуження щось робить» переходимо до «розгалуження вибирає значення».

Ключова форма виглядає так:

val message = when (x) {
    1 -> "..."
    2 -> "..."
    else -> "..."
}

Тут message — звичайна змінна, а when (...) {...} — вираз, який «обчислюється» в одне значення.

Приклад 1: вибираємо повідомлення за кодом відповіді

fun main() {
    val code = 404

    val message = when (code) {
        200 -> "OK"
        404 -> "Not Found"
        else -> "Інший код: $code"
    }

    println(message) // Not Found
}

Зверніть увагу: ми не друкуємо в гілках. Ми повертаємо рядки, а друк робимо один раз — після when. Це одразу наводить лад: спочатку вирішуємо, що сказати, а потім говоримо.

3. Вичерпність і else

Тепер головний «стопкадр» цієї лекції. Коли when використовується як оператор (statement), Kotlin дозволяє не покривати всі варіанти: якщо жодна гілка не підійшла, просто нічого не станеться. Офіційний приклад показує саме таку поведінку: не всі випадки покрито, гілку не вибрано, але помилки немає.

Але коли when використовується як вираз, Kotlin вимагає вичерпності: у виразу має бути результат за будь-якого входу. Інакше що присвоювати змінній? «Порожнечу»? Компілятор на таке не погоджується.

Kotlin формулює це прямо: якщо when використовується як вираз, ви повинні покрити всі випадки — інакше буде помилка компіляції.

Як виглядає помилка в реальному житті

Уявіть, що ви написали так:

fun main() {
    val code = 500

    val message = when (code) {
        200 -> "OK"
        404 -> "Not Found"
        // else немає
    }

    println(message)
}

Логіка зрозуміла: «мені поки важливі лише 200 і 404». Але компілятор скаже приблизно таке: «а що робити, якщо code дорівнює 500?» — і матиме рацію. Саме тому у when як виразі найчастіше зʼявляється else.

«else не обовʼязковий» — але не поспішаймо радіти

Теоретично else можна не писати, якщо ви гарантовано перелічили всі варіанти. У Kotlin є типи, для яких це справді працює (наприклад, деякі закриті набори значень), але там зʼявляються конструкції, які ми в курсі детально розбиратимемо пізніше.

На вашому поточному рівні простіше запамʼятати чесне правило для початківців: якщо when повертає значення, то майже завжди потрібен else.

4. Значення гілки: -> і блок { ... }

Коли гілка when — це один рядок після ->, усе просто: це й є значення. Але доволі швидко вам захочеться зробити в гілці кілька кроків: підготувати текст, трохи нормалізувати дані або порахувати щось поетапно. Тоді гілка стає блоком { ... }.

І тут важливо знати правило: значення гілки-блоку — це останній вираз усередині блоку. Kotlin підкреслює, що гілка може бути блоком, а її значенням буде значення останнього виразу цього блоку.

Приклад 2: гілка-блок повертає останній вираз

fun main() {
    val code = 400

    val message = when (code) {
        200 -> "OK"
        400 -> {
            val base = "Bad Request"
            "$base (перевірте вхідні дані)"
        }
        else -> "Інший код: $code"
    }

    println(message) // Bad Request (перевірте вхідні дані)
}

Тут ми всередині гілки підготували змінну base, а повернули рядок-шаблон — останнім рядком.

Нюанс, який економить години життя

Якщо останнім рядком у гілці буде println(...), то гілка поверне Unit, і далі почнеться «веселе»: різні гілки повертають різні типи. Тому, коли ви використовуєте when як вираз, намагайтеся, щоб гілки повертали значення, а друк залишайте зовні.

5. Тип результату when: усі гілки мають узгоджуватися

Коли when — вираз, у нього один результат, а отже — і один тип. Kotlin спробує вивести спільний тип для всіх гілок. Якщо одна гілка повертає String, інша — Int, а третя взагалі — Unit, то спільний тип стає надто «широким» (наприклад, Any), і далі таким результатом незручно користуватися.

Замість технічного формулювання запамʼятаймо практичне: заздалегідь визначте, що саме повертає ваш when. Якщо це повідомлення — нехай усі гілки повертають String. Якщо це число — нехай повертають число.

Приклад 3: when повертає число

fun main() {
    val month = 2

    val daysInMonth = when (month) {
        1, 3, 5, 7, 8, 10, 12 -> 31
        4, 6, 9, 11 -> 30
        2 -> 28
        else -> 0
    }

    println(daysInMonth) // 28
}

Так, тут ми використали групування значень через кому — це легально й читабельно. Kotlin показує такий стиль як стандартний прийом для when.

А else -> 0 тут не «для краси»: він закриває випадок «користувач увів 13». Ми поки не валідуємо діапазони «по-дорослому» — це буде наступна лекція, — але вичерпність потрібна вже зараз.

6. Практика: when у функціях, з null і без else

У реальних програмах розгалуження часто живе всередині функцій: «отримай код — поверни текст», «отримай команду — поверни підказку». Kotlin навіть у рекомендаціях зі стилю радить використовувати виразну форму if/when/try там, де ви повертаєте значення: це робить код коротшим і прямішим.

Далі — кілька типових ситуацій, у яких when як вираз особливо доречний.

when + функції: повертаємо значення без плутанини з if

Зробімо невелику функцію для майбутнього консольного застосунку (у наступних лекціях ми почнемо обробляти команди). Поки без введення — лише щоб побачити підхід: функція повертає рядок.

fun statusText(code: Int): String = when (code) {
    200 -> "OK"
    400 -> "Bad Request"
    404 -> "Not Found"
    else -> "Unknown: $code"
}

fun main() {
    println(statusText(404)) // Not Found
}

Це читається майже як словник: «200 — OK, 400 — Bad Request…». І головне: функція завжди повертає рядок, бо when вичерпний.

Nullable-значення в when: окрема гілка null

Зараз буде дуже життєва історія: користувач щось увів, ви спробували розпарсити, отримали Int? (тобто «число або null»), і далі потрібно коректно відреагувати.

when особливо зручний тут тим, що null можна обробити окремою гілкою. Це природно поєднується з вашим попереднім досвідом із toIntOrNull() і базовою null-safety.

fun main() {
    print("Введіть вік: ")
    val raw = readln().trim()

    val age: Int? = raw.toIntOrNull()

    val message = when (age) {
        null -> "Це не схоже на число: \"$raw\""
        0 -> "Вік 0 — звучить як старт карʼєри програміста."
        else -> "Ваш вік: $age"
    }

    println(message)
}

Тут важливо, що message — рядок за будь-якого сценарію. Навіть якщо age == null, ми все одно повертаємо рядок, отже when вичерпний.

Мінірефакторинг «консольного помічника»: спочатку формуємо відповідь, потім друкуємо

Зараз ми почнемо складати стиль, який буде особливо корисним у лекції про команди. Уявімо, що в нас є «консольний помічник» — застосунок, який реагує на команду й виводить текст. Поки без readln() і нормалізації (це буде наступна лекція) — лише начерк.

Ключова ідея цієї лекції: розгалуження повертає рядок, а друк робиться один раз.

fun replyFor(command: String): String = when (command) {
    "help" -> "Доступні команди: help, exit"
    "exit" -> "Завершую роботу..."
    else -> "Невідома команда: $command"
}

fun main() {
    val cmd = "help"

    val reply = replyFor(cmd)
    println(reply) // Доступні команди: help, exit
}

Подивіться, як зручно: replyFor(...) можна потім перевикористати. У наступній лекції ви навчитеся «діставати» command із користувацького введення, нормалізувати trim().lowercase(), і тоді when стане справжнім диспетчером команд. Але фундамент — саме тут: when як вираз повертає результат.

when без else: приклад для Boolean

Корисно побачити випадок, коли else справді не потрібен. Так ви зрозумієте суть вичерпності, а не просто запамʼятаєте «пишіть else».

У Boolean лише два значення. Якщо ви обробили true і false, то ви обробили взагалі все. У цьому випадку when вичерпний без else.

fun main() {
    val isAdmin = true

    val access = when (isAdmin) {
        true -> "Доступ: повний"
        false -> "Доступ: обмежений"
    }

    println(access) // Доступ: повний
}

Чому я все одно не раджу вам фанатично уникати else? Тому що щойно зʼявиться nullable-логіка (Boolean?) або складніший тип, «усе покрито» раптом перестане бути очевидним. На вашому поточному етапі else — не ворог, а страховка від сюрпризів і з боку компілятора, і з боку користувача.

7. Типові помилки під час використання when як виразу

Помилка №1: забути else і дивуватися, чому «раптом компілятор свариться».
Це найчастіша ситуація: ви написали гарне розгалуження, присвоїли його в val і очікуєте, що програма просто «нічого не зробить», якщо варіант не підійде. Але вираз зобовʼязаний повернути значення, тому else (або повний набір варіантів) потрібен обовʼязково. Це не примха Kotlin — це проста логіка: інакше незрозуміло, що саме присвоювати.

Помилка №2: повертати з різних гілок різні типи й отримати дивний спільний тип.
Наприклад, в одній гілці повернути "OK", в іншій — 0, а в третій викликати println(...). Kotlin почне шукати спільний тип, і ви отримаєте результат, з яким незручно працювати. Хороший стиль — заздалегідь вибрати «контракт» when: або повертаємо рядок, або повертаємо число — і дотримуємося цього в усіх гілках.

Помилка №3: друкувати (println) усередині гілок, а потім намагатися використати результат.
Це пастка «я звик до if/else, де я виконував дію». Якщо всередині гілки стоїть println(...), то це дія, а не значення. У результаті ви або не зможете присвоїти результат, або отримаєте Unit. Практичний прийом: спочатку обчисліть val message = when (...) { ... }, а потім зробіть один println(message) зовні.

Помилка №4: робити гілки-блоки, але забувати, що повертається останній вираз.
Іноді людина пише в гілці блок, створює змінні, а останнім рядком випадково залишає щось «не те» (наприклад, виклик функції, що повертає Unit). У підсумку гілка повертає Unit, і в when ламається тип результату. Якщо гілка — блок, подумки запитайте себе: «який тут останній рядок і що він повертає?» Kotlin прямо описує правило «останній вираз — значення гілки».

Помилка №5: змішувати у when одразу все: і обчислення, і введення, і друк, і обробку помилок.
Навіть якщо це працює, читати такий код важко: when перетворюється на маленьку «програму всередині програми». Тримайте гілки короткими: нехай when відповідає на питання «який результат вибрати?», а інші дії (введення/друк) живуть поруч і зрозумілі окремо. Цей стиль — одна з причин, чому Kotlin у домовленостях щодо коду надає перевагу виразній формі умовних конструкцій: вона робить логіку прямою.

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