Added kotlin guide. (WIP) (15.10.26)

This commit is contained in:
2026-10-15 00:24:21 +03:00
parent dc82207723
commit da3ad9dd1a
13 changed files with 2120 additions and 0 deletions
@@ -0,0 +1,241 @@
---
title: Умные приведения
description: Smart cast после is и проверки на null, условия работы и случаи, когда компилятор отказывается
tags:
- kotlin
- null-safety
- types
---
Умное приведение (smart cast) - автоматическое приведение типа после проверки. Компилятор отслеживает поток управления и знает, что в этой ветке тип уже уточнён.
```kotlin
fun describe(value: Any): String {
if (value is String) {
return "строка длины ${value.length}" // value уже String
}
return "не строка"
}
```
Явного `(String) value` писать не нужно - в отличие от Java (до версии 16).
## После проверки типа - is
```kotlin
fun size(x: Any): Int = when (x) {
is String -> x.length // String
is List<*> -> x.size // List<*>
is Map<*, *> -> x.size // Map<*, *>
else -> 0
}
```
`!is` работает так же, с инверсией:
```kotlin
fun check(x: Any) {
if (x !is String) return
println(x.length) // после return здесь только String
}
```
## После проверки на null
```kotlin
fun length(s: String?): Int {
if (s == null) return 0
return s.length // s: String
}
fun length2(s: String?): Int {
if (s != null) {
return s.length // s: String
}
return 0
}
```
## В логических выражениях
Приведение работает внутри `&&` и `||`, так как они ленивые:
```kotlin
fun check(x: Any?): Boolean {
return x is String && x.length > 3 // x приведён во второй части
}
fun check2(x: Any?): Boolean {
return x !is String || x.length > 3 // приведён в правой части ||
}
```
## В elvis и безопасных вызовах
```kotlin
fun process(s: String?) {
val text = s ?: return // после этой строки s... нет, но text: String
println(text.length)
}
```
## Где приведение НЕ работает
Главное условие: компилятор должен гарантировать, что **значение не изменится между проверкой и использованием**.
### Изменяемое свойство класса - var
```kotlin
class Holder {
var value: String? = null
fun broken() {
if (value != null) {
// println(value.length) // ошибка: smart cast to 'String' is impossible
}
}
fun fixed() {
val local = value // копия в локальную val
if (local != null) {
println(local.length) // работает
}
}
}
```
Почему: между проверкой и обращением другой поток (или вызванная функция) может присвоить `value = null`.
### Свойство с кастомным геттером
```kotlin
class Random2 {
val value: String?
get() = if (Math.random() > 0.5) "x" else null
fun broken() {
if (value != null) {
// println(value.length) // ошибка: геттер вернёт разное
}
}
}
```
### open-свойство
```kotlin
open class Base {
open val value: String? = "x"
}
fun check(b: Base) {
if (b.value != null) {
// println(b.value.length) // ошибка: наследник мог переопределить геттер
}
}
```
### Свойство из другого модуля
Даже `val` из другой единицы компиляции не приводится: компилятор не может доказать, что у него нет кастомного геттера.
### Локальная var, захваченная лямбдой
```kotlin
fun broken() {
var s: String? = "text"
val modify = { s = null }
if (s != null) {
// println(s.length) // ошибка: лямбда может изменить s
}
}
```
## Сводка условий
| Что проверяем | Умное приведение |
|---|---|
| локальная `val` | ✅ работает |
| локальная `var` без захвата лямбдой | ✅ работает |
| локальная `var`, захваченная лямбдой | ❌ |
| `val`-свойство в том же модуле, без геттера | ✅ работает |
| `var`-свойство | ❌ |
| `open val` | ❌ |
| `val` с кастомным геттером | ❌ |
| `val` из другого модуля | ❌ |
| параметр функции (всегда `val`) | ✅ работает |
Универсальное решение во всех ❌-случаях: скопировать значение в локальную `val`.
## Явное приведение - as
Когда умное не применимо:
```kotlin
val x: Any = "text"
val s = x as String // ClassCastException при несовпадении
val n = x as? Int // null при несовпадении
```
Подробнее: [Приведение типов](../Работа%20с%20данными/Приведение%20типов.md)
## Умное приведение в Kotlin 2.x
Компилятор K2 (по умолчанию с Kotlin 2.0) расширил анализ. Теперь работает то, что раньше требовало ручного приведения:
```kotlin
fun test(x: Any?) {
val isString = x is String
if (isString) {
println(x.length) // K2 понимает связь через переменную-флаг
}
}
fun orElse(x: Any?) {
if (x !is String) return
println(x.length) // работало и раньше
}
```
Подробнее: [Компилятор K2](../../Инструментарий/Компилятор%20K2.md)
> [!info] Отличия от Java
> | Java | Kotlin |
> |---|---|
> | `if (x instanceof String) { String s = (String) x; ... }` | `if (x is String) { x.length }` |
> | pattern matching `x instanceof String s` (Java 16+) | есть с первой версии, без объявления переменной |
> | приведение только по типу | ещё и по null |
> | компилятор не отслеживает изменяемость | отказывается приводить, если значение может измениться |
>
> Java 16 добавила pattern matching: `if (x instanceof String s)`. Это ближе к Kotlin, но Java всё равно требует явно назвать переменную, и на nullability это не распространяется.
>
> Сравнить: [Приведение ссылочных типов в Java](../../../Программирование%20на%20Java/Синтаксис%20и%20типы/Работа%20с%20данными%20и%20типами/Приведение%20ссылочных%20типов.md)
> [!warning] Подводные камни
> **Сообщение компилятора объясняет причину** - его стоит читать целиком:
> ```
> smart cast to 'String' is impossible, because 'value' is a mutable property
> that could be mutated concurrently
>
> smart cast to 'String' is impossible, because 'value' is a property
> that has an open or custom getter
>
> smart cast to 'String' is impossible, because 's' is a local variable
> that is mutated in a capturing closure
> ```
> Там прямо сказано и что мешает, и почему.
>
> **`!!` вместо локальной `val` - плохой обмен.** Оба компилируются, но второй вариант предсказуем:
> ```kotlin
> // println(value!!.length) // падение при гонке
> val v = value ?: return // гонки нет
> ```
>
> **Приведение дженериков неполное из-за стирания типов:**
> ```kotlin
> fun check(x: Any) {
> if (x is List<*>) {
> // элементы остаются Any?, тип параметра неизвестен
> val first = x.firstOrNull() // Any?
> }
> // if (x is List<String>) // ошибка: cannot check for instance of erased type
> }
> ```
> Обход - [reified](../../Дженерики/Reified%20и%20inline.md).
>
> **Приведение теряется после вызова функции, меняющей состояние** - если это свойство, а не локальная переменная:
> ```kotlin
> class C {
> var s: String? = "x"
> fun reset() { s = null }
> }
> ```
>
> **В `when` без аргумента приведение работает только для проверенной переменной:**
> ```kotlin
> fun combine(a: Any?, b: Any?): Int = when {
> a is String && b is Int -> a.length + b // оба приведены
> else -> 0
> }
> ```
Далее: [Платформенные типы](Платформенные%20типы.md)