8.3 KiB
title, description, tags
| title | description | tags | ||
|---|---|---|---|---|
| Условные конструкции | if и when как выражения, исчерпывающий when, отсутствие тернарного оператора и switch |
|
В 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только по числам, строкам, enumwhenпо любым значениям и условиямнужен break, иначе fall-throughпровала нет, breakне нуженc ? a : bif (c) a else bif- только инструкция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
Далее: Циклы и диапазоны