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