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

176 lines
8.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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<String>` написать нельзя. Подробнее: [Reified и inline](../../Дженерики/Reified%20и%20inline.md)
Далее: [Циклы и диапазоны](Циклы%20и%20диапазоны.md)