Kotlin guide done. (20.10.26)
This commit is contained in:
@@ -0,0 +1,254 @@
|
||||
---
|
||||
title: Приведение типов
|
||||
description: Проверка is, приведение as и as?, числовые преобразования и стирание типов
|
||||
tags:
|
||||
- kotlin
|
||||
- types
|
||||
---
|
||||
|
||||
## Проверка типа - is
|
||||
```kotlin
|
||||
fun describe(x: Any): String = when {
|
||||
x is String -> "строка длины ${x.length}"
|
||||
x is Int -> "число ${x + 1}"
|
||||
x !is Boolean -> "не логическое"
|
||||
else -> "логическое"
|
||||
}
|
||||
|
||||
fun main() {
|
||||
println(describe("ab")) // строка длины 2
|
||||
println(describe(5)) // число 6
|
||||
println(describe(1.0)) // не логическое
|
||||
}
|
||||
```
|
||||
После успешной проверки компилятор сам приводит тип - это [умное приведение](../Null-безопасность/Умные%20приведения.md), явный каст не нужен.
|
||||
|
||||
## Небезопасное приведение - as
|
||||
```kotlin
|
||||
fun main() {
|
||||
val x: Any = "text"
|
||||
val s = x as String // ok
|
||||
println(s.length) // 4
|
||||
|
||||
val n: Any = 42
|
||||
// val bad = n as String // ClassCastException
|
||||
}
|
||||
```
|
||||
`as` используют, когда тип известен точно, а компилятор об этом знать не может.
|
||||
|
||||
## Безопасное приведение - as?
|
||||
Возвращает `null` вместо исключения:
|
||||
```kotlin
|
||||
fun main() {
|
||||
val x: Any = "text"
|
||||
println(x as? Int) // null
|
||||
println(x as? String) // text
|
||||
println((x as? Int) ?: 0) // 0 — частая связка с elvis
|
||||
}
|
||||
```
|
||||
Результат всегда nullable, даже при приведении к non-null типу.
|
||||
|
||||
Сравнение способов:
|
||||
|
||||
| Запись | При несовпадении типа |
|
||||
|---|---|
|
||||
| `x is T` | `false` |
|
||||
| `x as T` | `ClassCastException` |
|
||||
| `x as? T` | `null` |
|
||||
|
||||
## Приведение к nullable
|
||||
```kotlin
|
||||
fun main() {
|
||||
val a: Any? = null
|
||||
println(a as? String) // null
|
||||
// println(a as String) // NPE: null cannot be cast to non-null type
|
||||
println(a as String?) // null — приведение к nullable разрешено
|
||||
}
|
||||
```
|
||||
|
||||
## Числовые преобразования
|
||||
Для чисел `as` не применяется - только явные функции:
|
||||
```kotlin
|
||||
fun main() {
|
||||
val i = 42
|
||||
println(i.toLong()) // 42
|
||||
println(i.toDouble()) // 42.0
|
||||
println(i.toByte()) // 42
|
||||
println(i.toChar()) // *
|
||||
println(3.99.toInt()) // 3 — обрезание, не округление
|
||||
println("42".toInt()) // 42
|
||||
}
|
||||
```
|
||||
Неявных преобразований между числовыми типами нет:
|
||||
```kotlin
|
||||
fun main() {
|
||||
val i: Int = 42
|
||||
// val l: Long = i // ошибка компиляции
|
||||
val l: Long = i.toLong() // так
|
||||
}
|
||||
```
|
||||
|
||||
Потеря данных происходит молча:
|
||||
```kotlin
|
||||
fun main() {
|
||||
println(300.toByte()) // 44 — старшие биты отброшены
|
||||
println(1e20.toInt()) // 2147483647 — обрезано
|
||||
println(Double.NaN.toInt()) // 0
|
||||
}
|
||||
```
|
||||
Подробнее: [Числа и переполнение](Числа%20и%20переполнение.md)
|
||||
|
||||
## Стирание типов и дженерики
|
||||
Параметры типа стираются при компиляции, поэтому проверить их в рантайме нельзя:
|
||||
```kotlin
|
||||
fun check(x: Any) {
|
||||
if (x is List<*>) { // можно: звёздочка вместо типа
|
||||
println("список из ${x.size}")
|
||||
}
|
||||
// if (x is List<String>) // ошибка: cannot check for instance
|
||||
// // of erased type
|
||||
}
|
||||
|
||||
fun main() { check(listOf(1, 2)) } // список из 2
|
||||
```
|
||||
`List<*>` - это star-проекция: «список чего-то неизвестного». Элементы из него читаются как `Any?`.
|
||||
|
||||
Приведение к конкретному дженерику компилируется с предупреждением и небезопасно:
|
||||
```kotlin
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T> castList(x: Any): List<T> = x as List<T> // unchecked cast
|
||||
|
||||
fun main() {
|
||||
val nums: List<Int> = castList(listOf("a", "b")) // не упадёт здесь!
|
||||
// println(nums.first() + 1) // ClassCastException только при обращении
|
||||
println(nums.size) // 2 — работает, типы не проверяются
|
||||
}
|
||||
```
|
||||
Это отложенное падение: ошибка проявится далеко от места приведения.
|
||||
|
||||
Обойти стирание можно через `reified` в inline-функциях:
|
||||
```kotlin
|
||||
inline fun <reified T> Any.safeCast(): T? = this as? T
|
||||
|
||||
fun main() {
|
||||
println("text".safeCast<String>()) // text
|
||||
println("text".safeCast<Int>()) // null
|
||||
}
|
||||
```
|
||||
Подробнее: [Reified и inline](../../Дженерики/Reified%20и%20inline.md)
|
||||
|
||||
## Приведение в иерархии
|
||||
```kotlin
|
||||
open class Animal(val name: String)
|
||||
class Dog(name: String) : Animal(name) {
|
||||
fun bark() = "Гав"
|
||||
}
|
||||
|
||||
fun main() {
|
||||
val a: Animal = Dog("Рекс") // вверх по иерархии — неявно
|
||||
println(a.name) // Рекс
|
||||
|
||||
val d = a as Dog // вниз — только явно
|
||||
println(d.bark()) // Гав
|
||||
|
||||
val other: Animal = Animal("кто-то")
|
||||
println((other as? Dog)?.bark()) // null — безопасно
|
||||
}
|
||||
```
|
||||
Приведение вверх (к супертипу) всегда безопасно и неявно. Приведение вниз требует `as` или `as?`.
|
||||
|
||||
## Коллекции и вариантность
|
||||
```kotlin
|
||||
fun main() {
|
||||
val strings: List<String> = listOf("a")
|
||||
val anys: List<Any> = strings // ok — List ковариантен по out T
|
||||
println(anys.size) // 1
|
||||
|
||||
val mStrings: MutableList<String> = mutableListOf("a")
|
||||
// val mAnys: MutableList<Any> = mStrings // ошибка — MutableList инвариантен
|
||||
}
|
||||
```
|
||||
`List<out T>` разрешает такое присваивание, `MutableList<T>` - нет, иначе можно было бы положить `Int` в список строк. Подробнее: [Вариантность](../../Дженерики/Вариантность.md)
|
||||
|
||||
## Получение типа в рантайме
|
||||
```kotlin
|
||||
fun main() {
|
||||
val x: Any = "text"
|
||||
println(x::class) // class kotlin.String
|
||||
println(x::class.simpleName) // String
|
||||
println(x::class.java) // class java.lang.String — Java-класс
|
||||
println(x is CharSequence) // true — проверка по интерфейсу
|
||||
}
|
||||
```
|
||||
Подробнее: [Рефлексия](../../Рефлексия/Рефлексия.md)
|
||||
|
||||
> [!info] Отличия от Java
|
||||
> | Java | Kotlin |
|
||||
> |---|---|
|
||||
> | `x instanceof String` | `x is String` |
|
||||
> | `(String) x` | `x as String` |
|
||||
> | нет безопасного каста | `x as? String` |
|
||||
> | `(int) doubleValue` | `doubleValue.toInt()` |
|
||||
> | `long l = intValue;` неявно | `intValue.toLong()` явно |
|
||||
> | `x.getClass()` | `x::class` или `x::class.java` |
|
||||
> | после `instanceof` нужен каст | умное приведение |
|
||||
> | массивы ковариантны | массивы инвариантны |
|
||||
>
|
||||
> Главные приобретения: `as?` вместо пары `instanceof` + каст, и запрет неявных числовых преобразований, из-за которых в Java `long total = intA * intB` тихо переполняется.
|
||||
>
|
||||
> Ковариантность массивов в Java - известная дыра в типизации:
|
||||
> ```java
|
||||
> Object[] objs = new String[1];
|
||||
> objs[0] = 42; // компилируется, падает в рантайме
|
||||
> ```
|
||||
> В Kotlin `Array<T>` инвариантен, и такой код не соберётся.
|
||||
>
|
||||
> Сравнить: [Приведение ссылочных типов в Java](../../../Программирование%20на%20Java/Синтаксис%20и%20типы/Работа%20с%20данными%20и%20типами/Приведение%20ссылочных%20типов.md)
|
||||
|
||||
> [!warning] Подводные камни
|
||||
> **`as` на null даёт NPE, а не ClassCastException:**
|
||||
> ```kotlin
|
||||
> fun main() {
|
||||
> val a: Any? = null
|
||||
> // println(a as String) // NullPointerException
|
||||
> println(a as String?) // null — ok
|
||||
> }
|
||||
> ```
|
||||
>
|
||||
> **`as?` маскирует обе причины.** `null` в результате означает либо неверный тип, либо что значение изначально было `null` - различить невозможно:
|
||||
> ```kotlin
|
||||
> fun main() {
|
||||
> val a: Any? = null
|
||||
> val b: Any = 42
|
||||
> println("${a as? String} ${b as? String}") // null null
|
||||
> }
|
||||
> ```
|
||||
>
|
||||
> **Unchecked cast падает не там, где написан:**
|
||||
> ```kotlin
|
||||
> @Suppress("UNCHECKED_CAST")
|
||||
> fun main() {
|
||||
> val list = listOf("a") as List<Int> // здесь не падает
|
||||
> println(list.size) // 1 — и здесь тоже
|
||||
> // println(list[0] + 1) // ClassCastException только тут
|
||||
> }
|
||||
> ```
|
||||
> Поэтому `@Suppress("UNCHECKED_CAST")` - всегда повод задуматься.
|
||||
>
|
||||
> **`toInt()` на `Double` обрезает дробную часть, а не округляет.** `3.99.toInt()` даёт `3`. Для округления - `roundToInt()`.
|
||||
>
|
||||
> **`toByte()` и `toShort()` молча теряют старшие биты:** `300.toByte()` возвращает `44`.
|
||||
>
|
||||
> **`is` не проверяет параметры дженерика.** `x is List<String>` не скомпилируется, а `x is List<*>` ничего не говорит об элементах. Проверять придётся вручную:
|
||||
> ```kotlin
|
||||
> fun main() {
|
||||
> val x: Any = listOf(1, "a")
|
||||
> val strings = (x as? List<*>)?.filterIsInstance<String>()
|
||||
> println(strings) // [a] — только элементы нужного типа
|
||||
> }
|
||||
> ```
|
||||
>
|
||||
> **`x::class` и `x::class.java` - разные вещи.** Первое - `KClass` Kotlin, второе - `Class` Java. Для `Int` они могут отличаться (`int` против `Integer`).
|
||||
|
||||
Дополнительно: [Умные приведения](../Null-безопасность/Умные%20приведения.md)
|
||||
Далее: [Any, Unit, Nothing](../Базовые%20типы%20ООП/Any,%20Unit,%20Nothing.md)
|
||||
Reference in New Issue
Block a user