Spec-Zone.ru › Kotlin 1.6

Проверка типов и приведение типов

Операторы is и !is

Используйте оператор is или его отрицание !is для выполнения проверки во время выполнения, которая определяет, соответствует ли объект заданному типу:

if (obj is String) {
    print(obj.length)
}

if (obj !is String) { // same as !(obj is String)
    print("Not a String")
} else {
    print(obj.length)
}

Умные приведения типов

В большинстве случаев вам не нужно использовать явные операторы приведения типов в Kotlin, потому что компилятор отслеживает проверки is и явные приведения для неизменяемых значений и вставляет (безопасные) приведения автоматически, когда это необходимо:

fun demo(x: Any) {
    if (x is String) {
        print(x.length) // x is automatically cast to String
    }
}

Компилятор достаточно умен, чтобы понимать, что приведение безопасно, если отрицательная проверка приводит к возврату:

if (x !is String) return

print(x.length) // x is automatically cast to String

или если оно находится в правой части && или ||, а соответствующая проверка (обычная или отрицательная) находится в левой части:

// x is automatically cast to String on the right-hand side of `||`
if (x !is String || x.length == 0) return

// x is automatically cast to String on the right-hand side of `&&`
if (x is String && x.length > 0) {
    print(x.length) // x is automatically cast to String
}

Умные приведения типов работают и для when выражений и while циклов:

when (x) {
    is Int -> print(x + 1)
    is String -> print(x.length + 1)
    is IntArray -> print(x.sum())
}

Обратите внимание, что умные приведения типов работают только тогда, когда компилятор может гарантировать, что переменная не изменится между проверкой и использованием. Более конкретно, умные приведения типов могут быть использованы при следующих условиях:

  • val локальные переменные — всегда, за исключением локальных делегированных свойств.

  • val свойства — если свойство является закрытым или внутренним, или если проверка выполняется в том же модуле, где объявлено свойство. Умные приведения типов не могут быть использованы для открытых свойств или свойств с пользовательскими геттерами.

  • var локальные переменные — если переменная не изменяется между проверкой и использованием, не захвачена в лямбда-выражение, которое изменяет ее, и не является локальным делегированным свойством.

  • var свойства — никогда, потому что переменная может быть изменена в любое время другим кодом.

Оператор "небезопасного" приведения типа

Обычно оператор приведения типа выбрасывает исключение, если приведение невозможно. Поэтому он называется небезопасным. Небезопасное приведение в Kotlin выполняется с помощью инфиксного оператора as.

val x: String = y as String

Обратите внимание, что null не может быть приведено к String, так как этот тип не является nullable. Если y равно null, приведенный выше код выбросит исключение. Чтобы сделать такой код корректным для null-значений, используйте nullable-тип в правой части приведения:

val x: String? = y as String?

Оператор "безопасного" (nullable) приведения типа

Чтобы избежать исключений, используйте оператор безопасного приведения as?, который возвращает null при неудаче.

val x: String? = y as? String

Обратите внимание, что несмотря на то, что правая часть as? — это не nullable-тип String, результат приведения является nullable.

Стирание типов и проверки типов дженериков

Kotlin обеспечивает безопасность типов для операций с дженериками на этапе компиляции, в то время как во время выполнения экземпляры дженерических типов не содержат информации об их фактических аргументах типов. Например, List<Foo> стирается до простого List<*>. В общем случае нет способа проверить, принадлежит ли экземпляр дженерическому типу с определенными аргументами типа во время выполнения.

Из-за этого компилятор запрещает проверки is, которые не могут быть выполнены во время выполнения из-за стирания типов, таких как ints is List<Int> или list is T (параметр типа). Однако вы можете проверить экземпляр на соответствие звездообразному типу:

if (something is List<*>) {
    something.forEach { println(it) } // The items are typed as `Any?`
}

Аналогично, когда у вас уже есть аргументы типа экземпляра, проверенные статически (на этапе компиляции), вы можете выполнить проверку is или приведение, которое включает в себя не дженерическую часть типа. Обратите внимание, что угловые скобки в этом случае опущены:

fun handleStrings(list: List<String>) {
    if (list is ArrayList) {
        // `list` is smart-cast to `ArrayList<String>`
    }
}

Такую же синтаксическую конструкцию, но с опущенными аргументами типа, можно использовать для приведений, которые не учитывают аргументы типа: list as ArrayList.

Встроенные функции с реализованными параметрами типа имеют свои фактические аргументы типа, встроенные в каждой точке вызова. Это позволяет выполнять проверки arg is T для параметров типа, но если arg является экземпляром дженерического типа, то его аргументы типа все равно стираются.

//sampleStart
inline fun <reified A, reified B> Pair<*, *>.asPairOf(): Pair<A, B>? {
    if (first !is A || second !is B) return null
    return first as A to second as B
}

val somePair: Pair<Any?, Any?> = "items" to listOf(1, 2, 3)


val stringToSomething = somePair.asPairOf<String, Any>()
val stringToInt = somePair.asPairOf<String, Int>()
val stringToList = somePair.asPairOf<String, List<*>>()
val stringToStringList = somePair.asPairOf<String, List<String>>() // Compiles but breaks type safety!
// Expand the sample for more details

//sampleEnd

fun main() {
    println("stringToSomething = " + stringToSomething)
    println("stringToInt = " + stringToInt)
    println("stringToList = " + stringToList)
    println("stringToStringList = " + stringToStringList)
    //println(stringToStringList?.second?.forEach() {it.length}) // This will throw ClassCastException as list items are not String
}

Непроверенные приведения типов

Как было установлено выше, стирание типов делает невозможной проверку фактических аргументов типа дженерического типа экземпляра во время выполнения. Кроме того, дженерические типы в коде могут не быть достаточно тесно связаны друг с другом, чтобы компилятор гарантировал безопасность типа.

Тем не менее, иногда у нас есть логика программы высокого уровня, которая подразумевает безопасность типа. Например:

fun readDictionary(file: File): Map<String, *> = file.inputStream().use {
   TODO("Read a mapping of strings to arbitrary elements.")
}

// We saved a map with `Int`s into this file
val intsFile = File("ints.dictionary")

// Warning: Unchecked cast: `Map<String, *>` to `Map<String, Int>`
val intsDictionary: Map<String, Int> = readDictionary(intsFile) as Map<String, Int>

Для приведения в последней строке появляется предупреждение. Компилятор не может полностью проверить его во время выполнения и не предоставляет гарантии, что значения в карте являются Int.

Чтобы избежать непроверенных приведений, можно переработать структуру программы. В приведенном выше примере можно использовать интерфейсы DictionaryReader<T> и DictionaryWriter<T> с безопасными по типу реализациями для разных типов. Можно ввести разумные абстракции, чтобы перенести непроверенные приведения из точки вызова в детали реализации. Правильное использование вариантности дженериков также может помочь.

Для дженерических функций использование реализованных параметров типа делает приведения, такие как arg as T, проверенными, если у типа arg нет своих аргументов типа, которые стираются.

Предупреждение о непроверенном приведении можно подавить, маркируя оператор или объявление, где оно возникает, аннотацией @Suppress("UNCHECKED_CAST"):

inline fun <reified T> List<*>.asListOfType(): List<T>? =
    if (all { it is T })
        @Suppress("UNCHECKED_CAST")
        this as List<T> else
        null

В JVM: типы массивов (Array<Foo>) сохраняют информацию об отсортированном типе своих элементов, и приведения типов к типу массива частично проверяются: nullable и фактические типы аргументов элемента все равно стираются. Например, приведение foo as Array<List<String>?> пройдет, если foo является массивом, содержащим любые List<*>, будь то nullable или нет.

Последнее изменение: 07 апреля 2022
Основные типы Условные операторы и циклы

© 2010–2022 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/typecasts.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API