Kotlin guide done. (20.10.26)

This commit is contained in:
2026-10-20 12:54:25 +03:00
parent da3ad9dd1a
commit 6853ada348
81 changed files with 30112 additions and 0 deletions
@@ -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)