--- 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) // ошибка: cannot check for instance // // of erased type } fun main() { check(listOf(1, 2)) } // список из 2 ``` `List<*>` - это star-проекция: «список чего-то неизвестного». Элементы из него читаются как `Any?`. Приведение к конкретному дженерику компилируется с предупреждением и небезопасно: ```kotlin @Suppress("UNCHECKED_CAST") fun castList(x: Any): List = x as List // unchecked cast fun main() { val nums: List = castList(listOf("a", "b")) // не упадёт здесь! // println(nums.first() + 1) // ClassCastException только при обращении println(nums.size) // 2 — работает, типы не проверяются } ``` Это отложенное падение: ошибка проявится далеко от места приведения. Обойти стирание можно через `reified` в inline-функциях: ```kotlin inline fun Any.safeCast(): T? = this as? T fun main() { println("text".safeCast()) // text println("text".safeCast()) // 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 = listOf("a") val anys: List = strings // ok — List ковариантен по out T println(anys.size) // 1 val mStrings: MutableList = mutableListOf("a") // val mAnys: MutableList = mStrings // ошибка — MutableList инвариантен } ``` `List` разрешает такое присваивание, `MutableList` - нет, иначе можно было бы положить `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` инвариантен, и такой код не соберётся. > > Сравнить: [Приведение ссылочных типов в 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 // здесь не падает > 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` не скомпилируется, а `x is List<*>` ничего не говорит об элементах. Проверять придётся вручную: > ```kotlin > fun main() { > val x: Any = listOf(1, "a") > val strings = (x as? List<*>)?.filterIsInstance() > 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)