Files
obsidian/Kotlin/Синтаксис и типы/Базовый синтаксис/Условные конструкции.md
T

8.3 KiB
Raw Blame History

title, description, tags
title description tags
Условные конструкции if и when как выражения, исчерпывающий when, отсутствие тернарного оператора и switch
kotlin
syntax

В Kotlin две конструкции ветвления: if и when. Обе могут работать и как инструкция, и как выражение, возвращающее значение.

if как инструкция

if (x > 0) {
    println("положительное")
} else if (x < 0) {
    println("отрицательное")
} else {
    println("ноль")
}

Условие обязано быть Boolean. Число или null туда подставить нельзя - компилятор не пропустит.

if как выражение

val max = if (a > b) a else b

val label = if (x > 0) {
    log("positive")
    "плюс"            // последнее выражение блока — результат
} else {
    "минус"
}

Правила:

  • в режиме выражения ветка else обязательна - иначе значение не определено
  • результат блока - его последнее выражение
  • тип результата - общий супертип всех ветвей
val r = if (flag) 1 else "text"   // тип Any — общий супертип

Тернарного оператора ?: в смысле Java в Kotlin нет: его роль играет if. Оператор ?: в Kotlin - это elvis, у него другая задача (Операторы null-безопасности).

when - замена switch

Две формы. С аргументом:

when (code) {
    200 -> println("ok")
    404 -> println("not found")
    500, 502, 503 -> println("ошибка сервера")   // несколько значений в одной ветке
    else -> println("неизвестно")
}

Без аргумента - вместо цепочки if/else if:

when {
    x < 0 -> println("отрицательное")
    x == 0 -> println("ноль")
    else -> println("положительное")
}

Ветки проверяются сверху вниз, выполняется первая подошедшая. Никакого break не нужно - провала в следующую ветку (fall-through) в Kotlin не бывает.

when как выражение

val name = when (code) {
    200 -> "ok"
    404 -> "not found"
    else -> "unknown"
}

В режиме выражения when обязан быть исчерпывающим: либо есть else, либо компилятор видит, что перечислены все варианты.

Условия в ветках when

Не только константы:

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 переменная автоматически приводится к проверенному типу - см. Умные приведения.

Исчерпывающий when без else

С enum и sealed компилятор сам проверяет полноту:

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, Sealed class и interface

Переменная в when

Аргумент можно объявить прямо в заголовке - он будет виден только внутри:

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

[!warning] Подводные камни Полнота проверяется и у when-инструкции - но только для enum и sealed. С Kotlin 2.0 пропуск ветки - ошибка компиляции даже когда результат не используется:

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 компилируется молча и просто ничего не делает:

fun handle(code: Int) {
    when (code) {
        200 -> show()
        404 -> fail()
    }                  // для code = 500 тихо ничего не произойдёт
}

Это ещё одна причина моделировать состояния через enum и sealed, а не через числа и строки: только тогда компилятор на вашей стороне.

Порядок ветвей важен. Первая подошедшая выигрывает, более общая ветка выше «съест» частные:

when (x) {
    in 0..100 -> "диапазон"   // сработает и для 42
    42 -> "ответ"             // недостижимо
}

if без else не выражение:

// val x = if (flag) 1      // ошибка компиляции
val x = if (flag) 1 else 0  // ok

is List<*> не проверяет тип элементов - дженерики стираются в рантайме. is List<String> написать нельзя. Подробнее: Reified и inline

Далее: Циклы и диапазоны