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 в соглашениях по коду предпочитает выраженческую форму условных конструкций: она делает логику прямой.

1
Задача
Kotlin SELF, 13 уровень, 2 лекция
Недоступна
Кварталы отчёта
Кварталы отчёта
1
Задача
Kotlin SELF, 13 уровень, 2 лекция
Недоступна
Пропуск в админку
Пропуск в админку
1
Задача
Kotlin SELF, 13 уровень, 2 лекция
Недоступна
Статус платежа
Статус платежа
1
Задача
Kotlin SELF, 13 уровень, 2 лекция
Недоступна
Скидка на чеке
Скидка на чеке
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ