11 KiB
title, description, tags
| title | description | tags | ||
|---|---|---|---|---|
| Приведение типов | Проверка is, приведение as и as?, числовые преобразования и стирание типов |
|
Проверка типа - is
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)) // не логическое
}
После успешной проверки компилятор сам приводит тип - это умное приведение, явный каст не нужен.
Небезопасное приведение - as
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 вместо исключения:
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
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 не применяется - только явные функции:
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
}
Неявных преобразований между числовыми типами нет:
fun main() {
val i: Int = 42
// val l: Long = i // ошибка компиляции
val l: Long = i.toLong() // так
}
Потеря данных происходит молча:
fun main() {
println(300.toByte()) // 44 — старшие биты отброшены
println(1e20.toInt()) // 2147483647 — обрезано
println(Double.NaN.toInt()) // 0
}
Подробнее: Числа и переполнение
Стирание типов и дженерики
Параметры типа стираются при компиляции, поэтому проверить их в рантайме нельзя:
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?.
Приведение к конкретному дженерику компилируется с предупреждением и небезопасно:
@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-функциях:
inline fun <reified T> Any.safeCast(): T? = this as? T
fun main() {
println("text".safeCast<String>()) // text
println("text".safeCast<Int>()) // null
}
Подробнее: Reified и inline
Приведение в иерархии
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?.
Коллекции и вариантность
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 в список строк. Подробнее: Вариантность
Получение типа в рантайме
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 — проверка по интерфейсу
}
Подробнее: Рефлексия
[!info] Отличия от Java
Java Kotlin x instanceof Stringx is String(String) xx as Stringнет безопасного каста x as? String(int) doubleValuedoubleValue.toInt()long l = intValue;неявноintValue.toLong()явноx.getClass()x::classилиx::class.javaпосле instanceofнужен кастумное приведение массивы ковариантны массивы инвариантны Главные приобретения:
as?вместо парыinstanceof+ каст, и запрет неявных числовых преобразований, из-за которых в Javalong total = intA * intBтихо переполняется.Ковариантность массивов в Java - известная дыра в типизации:
Object[] objs = new String[1]; objs[0] = 42; // компилируется, падает в рантаймеВ Kotlin
Array<T>инвариантен, и такой код не соберётся.Сравнить: Приведение ссылочных типов в Java
[!warning] Подводные камни
asна null даёт NPE, а не ClassCastException:fun main() { val a: Any? = null // println(a as String) // NullPointerException println(a as String?) // null — ok }
as?маскирует обе причины.nullв результате означает либо неверный тип, либо что значение изначально былоnull- различить невозможно:fun main() { val a: Any? = null val b: Any = 42 println("${a as? String} ${b as? String}") // null null }Unchecked cast падает не там, где написан:
@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<*>ничего не говорит об элементах. Проверять придётся вручную:fun main() { val x: Any = listOf(1, "a") val strings = (x as? List<*>)?.filterIsInstance<String>() println(strings) // [a] — только элементы нужного типа }
x::classиx::class.java- разные вещи. Первое -KClassKotlin, второе -ClassJava. ДляIntони могут отличаться (intпротивInteger).
Дополнительно: Умные приведения Далее: Any, Unit, Nothing