Files
obsidian/Kotlin/Синтаксис и типы/Работа с данными/Приведение типов.md
T

255 lines
11 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: Проверка 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)