--- title: Условные конструкции description: if и when как выражения, исчерпывающий when, отсутствие тернарного оператора и switch tags: - kotlin - syntax --- В Kotlin две конструкции ветвления: `if` и `when`. Обе могут работать и как инструкция, и как **выражение**, возвращающее значение. ## if как инструкция ```kotlin if (x > 0) { println("положительное") } else if (x < 0) { println("отрицательное") } else { println("ноль") } ``` Условие обязано быть `Boolean`. Число или null туда подставить нельзя - компилятор не пропустит. ## if как выражение ```kotlin val max = if (a > b) a else b val label = if (x > 0) { log("positive") "плюс" // последнее выражение блока — результат } else { "минус" } ``` Правила: - в режиме выражения ветка `else` **обязательна** - иначе значение не определено - результат блока - его последнее выражение - тип результата - общий супертип всех ветвей ```kotlin val r = if (flag) 1 else "text" // тип Any — общий супертип ``` Тернарного оператора `?:` в смысле Java в Kotlin нет: его роль играет `if`. Оператор `?:` в Kotlin - это **elvis**, у него другая задача ([Операторы null-безопасности](../Null-безопасность/Операторы%20null-безопасности.md)). ## when - замена switch Две формы. С аргументом: ```kotlin when (code) { 200 -> println("ok") 404 -> println("not found") 500, 502, 503 -> println("ошибка сервера") // несколько значений в одной ветке else -> println("неизвестно") } ``` Без аргумента - вместо цепочки `if/else if`: ```kotlin when { x < 0 -> println("отрицательное") x == 0 -> println("ноль") else -> println("положительное") } ``` Ветки проверяются **сверху вниз**, выполняется первая подошедшая. Никакого `break` не нужно - провала в следующую ветку (fall-through) в Kotlin не бывает. ## when как выражение ```kotlin val name = when (code) { 200 -> "ok" 404 -> "not found" else -> "unknown" } ``` В режиме выражения `when` обязан быть **исчерпывающим**: либо есть `else`, либо компилятор видит, что перечислены все варианты. ## Условия в ветках when Не только константы: ```kotlin when (x) { in 1..9 -> "одна цифра" // диапазон in listOf(10, 20, 30) -> "круглое" // коллекция !in 0..100 -> "вне диапазона" else -> "прочее" } when (value) { is String -> value.length // проверка типа + умное приведение is Int -> value + 1 is List<*> -> value.size else -> 0 } when (x) { computeLimit() -> "предел" // произвольное выражение else -> "нет" } ``` После `is` переменная автоматически приводится к проверенному типу - см. [Умные приведения](../Null-безопасность/Умные%20приведения.md). ## Исчерпывающий when без else С `enum` и `sealed` компилятор сам проверяет полноту: ```kotlin enum class State { LOADING, READY, ERROR } fun describe(s: State) = when (s) { State.LOADING -> "загрузка" State.READY -> "готово" State.ERROR -> "ошибка" } // else не нужен — все варианты перечислены ``` Добавили `State.EMPTY` - код **перестанет компилироваться** во всех таких `when`. Это главная причина предпочитать `sealed` и `enum` строкам и числам: компилятор показывает каждое место, которое нужно обновить. Подробнее: [Enum class](../Базовые%20типы%20ООП/Enum%20class.md), [Sealed class и interface](../Базовые%20типы%20ООП/Sealed%20class%20и%20interface.md) ## Переменная в when Аргумент можно объявить прямо в заголовке - он будет виден только внутри: ```kotlin when (val response = request()) { is Success -> response.data is Failure -> response.error } ``` Удобно, когда `request()` дорогой: вызов происходит один раз. > [!info] Отличия от Java > | Java | Kotlin | > |---|---| > | `switch` только по числам, строкам, enum | `when` по любым значениям и условиям | > | нужен `break`, иначе fall-through | провала нет, `break` не нужен | > | `c ? a : b` | `if (c) a else b` | > | `if` - только инструкция | `if` - ещё и выражение | > | `switch` не проверяет полноту (до pattern matching в Java 21) | `when` по enum/sealed проверяется компилятором | > > `when` ближе всего к `switch`-выражению из Java 14+ и pattern matching из Java 21, но появился в Kotlin задолго до них и покрывает больше случаев. > > Сравнить: [Условные конструкции в Java](../../../Программирование%20на%20Java/Синтаксис%20и%20типы/Базовый%20синтаксис/Условные%20конструкции%20и%20операторы.md) > [!warning] Подводные камни > **Полнота проверяется и у `when`-инструкции - но только для `enum` и `sealed`.** С Kotlin 2.0 пропуск ветки - ошибка компиляции даже когда результат не используется: > ```kotlin > fun handle(s: State) { > when (s) { > State.LOADING -> show() > State.READY -> done() > } // error: 'when' expression must be exhaustive. > } // Add the 'ERROR' branch or an 'else' branch. > ``` > А вот для `Int`, `String` и прочих открытых типов полноту проверить невозможно - там `when`-инструкция без `else` компилируется молча и просто ничего не делает: > ```kotlin > fun handle(code: Int) { > when (code) { > 200 -> show() > 404 -> fail() > } // для code = 500 тихо ничего не произойдёт > } > ``` > Это ещё одна причина моделировать состояния через `enum` и `sealed`, а не через числа и строки: только тогда компилятор на вашей стороне. > > **Порядок ветвей важен.** Первая подошедшая выигрывает, более общая ветка выше «съест» частные: > ```kotlin > when (x) { > in 0..100 -> "диапазон" // сработает и для 42 > 42 -> "ответ" // недостижимо > } > ``` > > **`if` без `else` не выражение:** > ```kotlin > // val x = if (flag) 1 // ошибка компиляции > val x = if (flag) 1 else 0 // ok > ``` > > **`is List<*>` не проверяет тип элементов** - дженерики стираются в рантайме. `is List` написать нельзя. Подробнее: [Reified и inline](../../Дженерики/Reified%20и%20inline.md) Далее: [Циклы и диапазоны](Циклы%20и%20диапазоны.md)