Added kotlin guide. (WIP) (15.10.26)
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
---
|
||||
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)
|
||||
Reference in New Issue
Block a user