Spec-Zone.ru › Kotlin 2

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

В Kotlin во время выполнения с типами можно делать две вещи: проверять, является ли объект экземпляром определённого типа, или преобразовывать его в другой тип. Проверки типов помогают определить, с каким объектом вы работаете, а приведения типов пытаются преобразовать объект в другой тип.

Подробнее о проверках типов и приведениях типов для обобщений, например List<T>, Map<K,V>, см. в разделе Проверки типов и приведения типов для обобщений.

Проверки с помощью операторов is и !is

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

fun main() {
    val input: Any = "Hello, Kotlin"

    if (input is String) {
        println("Message length: ${input.length}")
        // Message length: 13
    }

    if (input !is String) { // Same as !(input is String)
        println("Input is not a valid message")
    } else {
        println("Processing message: ${input.length} characters")
        // Processing message: 13 characters
    }
}

Также можно использовать операторы is и !is, чтобы проверить, соответствует ли объект подтипу:

interface Animal {
    val name: String
    fun speak()
}

class Dog(override val name: String) : Animal {
    override fun speak() = println("$name says: Woof!")
}

class Cat(override val name: String) : Animal {
    override fun speak() = println("$name says: Meow!")
}
//sampleStart
fun handleAnimal(animal: Animal) {
    println("Handling animal: ${animal.name}")
    animal.speak()
    
    // Use is operator to check for subtypes
    if (animal is Dog) {
        println("Special care instructions: This is a dog.")
    } else if (animal is Cat) {
        println("Special care instructions: This is a cat.")
    }
}
//sampleEnd
fun main() {
    val pets: List<Animal> = listOf(
        Dog("Buddy"),
        Cat("Whiskers"),
        Dog("Rex")
    )

    for (pet in pets) {
        handleAnimal(pet)
        println("---")
    }
    // Handling animal: Buddy
    // Buddy says: Woof!
    // Special care instructions: This is a dog.
    // ---
    // Handling animal: Whiskers
    // Whiskers says: Meow!
    // Special care instructions: This is a cat.
    // ---
    // Handling animal: Rex
    // Rex says: Woof!
    // Special care instructions: This is a dog.
    // ---
}

В этом примере оператор is используется, чтобы проверить, является ли экземпляр класса Animal подтипом Dog или Cat, и вывести соответствующие инструкции по уходу.

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

Чтобы определить тип объекта во время выполнения, см. раздел Рефлексия.

Приведения типов

Преобразование типа объекта в Kotlin в другой тип называется приведением типа.

В некоторых случаях компилятор автоматически приводит типы объектов. Это называется умным приведением типа.

Если нужно явно привести тип, используйте операторы приведения as? или as .

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

Компилятор отслеживает проверки типов и явные приведения для неизменяемых значений и автоматически вставляет неявные (безопасные) приведения:

fun logMessage(data: Any) {
    // data is automatically cast to String
    if (data is String) {
        println("Received text: ${data.length} characters")
    }
}

fun main() {
    logMessage("Server started")
    // Received text: 14 characters
    logMessage(404)
}

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

fun logMessage(data: Any) {
    // data is automatically cast to String
    if (data !is String) return

    println("Received text: ${data.length} characters")
}

fun main() {
    logMessage("User signed in")
    // Received text: 14 characters
    logMessage(true)
}

Поток управления

Умные приведения типов работают не только для условных выражений if, но и для выражений when:

fun processInput(data: Any) {
    when (data) {
        // data is automatically cast to Int
        is Int -> println("Log: Assigned new ID ${data + 1}")
        // data is automatically cast to String
        is String -> println("Log: Received message \"$data\"")
        // data is automatically cast to IntArray
        is IntArray -> println("Log: Processed scores, total = ${data.sum()}")
    }
}

fun main() {
    processInput(1001)
    // Log: Assigned new ID 1002
    processInput("System rebooted")
    // Log: Received message "System rebooted"
    processInput(intArrayOf(10, 20, 30))
    // Log: Processed scores, total = 60
}

А также для циклов while:

sealed interface Status
data class Ok(val currentRoom: String) : Status
data object Error : Status

class RobotVacuum(val rooms: List<String>) {
    var index = 0

    fun status(): Status =
        if (index < rooms.size) Ok(rooms[index])
        else Error

    fun clean(): Status {
        println("Finished cleaning ${rooms[index]}")
        index++
        return status()
    }
}

fun main() {
    //sampleStart
    val robo = RobotVacuum(listOf("Living Room", "Kitchen", "Hallway"))

    var status: Status = robo.status()
    while (status is Ok) {
        // The compiler smart casts status to OK type, so the currentRoom
        // property is accessible.
        println("Cleaning ${status.currentRoom}...")
        status = robo.clean()
    }
    // Cleaning Living Room...
    // Finished cleaning Living Room
    // Cleaning Kitchen...
    // Finished cleaning Kitchen
    // Cleaning Hallway...
    // Finished cleaning Hallway
    //sampleEnd
}

В этом примере запечатанный интерфейс Status имеет две реализации: класс данных Ok и объект данных Error. Только у класса данных Ok есть свойство currentRoom. Когда условие цикла while принимает значение true, компилятор выполняет умное приведение переменной status к типу Ok, благодаря чему свойство currentRoom становится доступным в теле цикла.

Если объявить переменную типа Boolean перед использованием в условии if, when или while, любые сведения о переменной, собранные компилятором, будут доступны для умного приведения в соответствующем блоке.

Это может быть полезно, например, если нужно вынести логические условия в переменные. Тогда переменной можно дать говорящее имя, что улучшит читаемость кода и позволит повторно использовать её позже. Например:

class Cat {
    fun purr() {
        println("Purr purr")
    }
}
//sampleStart
fun petAnimal(animal: Any) {
    val isCat = animal is Cat
    if (isCat) {
        // The compiler can access information about
        // isCat, so it knows that animal was smart-cast
        // to the type Cat.
        // Therefore, the purr() function can be called.
        animal.purr()
    }
}

fun main(){
    val kitty = Cat()
    petAnimal(kitty)
    // Purr purr
}
//sampleEnd

Логические операторы

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

// 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
}

Если объединить проверки типов объектов оператором and (&&), компилятор одновременно выполняет умное приведение объекта ко всем проверяемым типам. Подробнее см. в разделе Пересечения типов.

Если объединить проверки типов объектов оператором or (||), выполняется умное приведение к их ближайшему общему супертипу:

interface Status {
    fun signal() {}
}

interface Ok : Status
interface Postponed : Status
interface Declined : Status

fun signalCheck(signalStatus: Any) {
    if (signalStatus is Postponed || signalStatus is Declined) {
        // signalStatus is smart-cast to a common supertype Status
        signalStatus.signal()
    }
}

Общий супертип является приближением к объединённому типу. В настоящее время объединённые типы не поддерживаются в Kotlin.

Пересечения типов

Когда компилятор выполняет умное приведение объекта по нескольким проверкам &&, он выводит пересечение типов. Это внутренний тип, который одновременно удовлетворяет всем проверяемым ограничениям:

interface Bird {
    fun fly()
}

interface Fish {
    fun swim()
}

fun describe(animal: Any) {
    // Infers the Bird and Fish types
    if (animal is Bird && animal is Fish) {
        // Accesses fly() and swim() without additional checks or casts
        animal.fly()
        animal.swim()
    }
}

Пересечения типов не имеют обозначения. Они существуют только во внутренней системе типов компилятора, чтобы сохранять информацию о типах во время проверки. Их нельзя напрямую записать в коде Kotlin. Пересечения типов могут встречаться в сообщениях об ошибках компилятора и подсказках IDE, обычно в виде A & B. Единственное исключение — T & Any, объявляющий определённо ненулевой тип. Этот синтаксис предназначен специально для объединения параметра типа с Any.

fun  <T> T.assertNotNull(): T & Any = this ?: throw IllegalStateException("null value")

Встраиваемые функции

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

Встраиваемые функции считаются имеющими неявный контракт callsInPlace. Это означает, что все лямбда-функции, переданные во встраиваемую функцию, вызываются непосредственно в месте вызова. Поскольку лямбда-функции вызываются непосредственно в месте вызова, компилятор знает, что лямбда-функция не может передать ссылки на переменные, содержащиеся в её теле.

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

interface Processor {
    fun process()
}

inline fun inlineAction(f: () -> Unit) = f()

fun nextProcessor(): Processor? = null

fun runProcessor(): Processor? {
    var processor: Processor? = null
    inlineAction {
        // The compiler knows that processor is a local variable and inlineAction()
        // is an inline function, so references to processor can't be leaked.
        // Therefore, it's safe to smart-cast processor.
      
        // If processor isn't null, processor is smart-cast
        if (processor != null) {
            // The compiler knows that processor isn't null, so no safe call 
            // is needed
            processor.process()
        }

        processor = nextProcessor()
    }

    return processor
}

Обработка исключений

Информация для умного приведения передаётся в блоки catch и finally. Это делает код безопаснее, поскольку компилятор отслеживает, имеет ли объект nullable-тип. Например:

//sampleStart
fun testString() {
    var stringInput: String? = null
    // stringInput is smart-cast to String type
    stringInput = ""
    try {
        // The compiler knows that stringInput isn't null
        println(stringInput.length)
        // 0

        // The compiler rejects previous smart cast information for 
        // stringInput. Now stringInput has the String? type.
        stringInput = null

        // Trigger an exception
        if (2 > 1) throw Exception()
        stringInput = ""
    } catch (exception: Exception) {
        // The compiler knows stringInput can be null
        // so stringInput stays nullable.
        println(stringInput?.length)
        // null
    }
}
//sampleEnd
fun main() {
    testString()
}

Условия для умного приведения типов

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

Локальные переменные val

Всегда, кроме локальных делегированных свойств.

Свойства val

Если свойство является private, internal или проверка выполняется в том же модуле, где объявлено свойство. Умные приведения типов нельзя использовать для свойств open и свойств с пользовательскими геттерами.

Локальные переменные var

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

Свойства var

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

Операторы приведения типов as и as?

В Kotlin есть два оператора приведения типов: as и as?. Оба можно использовать для приведения типов, но работают они по-разному.

Если приведение с помощью оператора as завершается неудачей, во время выполнения выбрасывается ClassCastException. Поэтому его также называют небезопасным оператором. as можно использовать при приведении к ненулевому типу:

fun main() {
    val rawInput: Any = "user-1234"

    // Casts to String successfully
    val userId = rawInput as String
    println("Logging in user with ID: $userId")
    // Logging in user with ID: user-1234

    // Triggers ClassCastException
    val wrongCast = rawInput as Int
    println("wrongCast contains: $wrongCast")
    // Exception in thread "main" java.lang.ClassCastException
}

Если вместо него использовать оператор as?, при неудачном приведении оператор вернёт null. Поэтому его также называют безопасным оператором:

fun main() {
    val rawInput: Any = "user-1234"

    // Casts to String successfully
    val userId = rawInput as? String
    println("Logging in user with ID: $userId")
    // Logging in user with ID: user-1234

    // Assigns a null value to wrongCast
    val wrongCast = rawInput as? Int
    println("wrongCast contains: $wrongCast")
    // wrongCast contains: null
}

Для безопасного приведения к nullable-типу используйте оператор as?, чтобы избежать выбрасывания ClassCastException при неудачном приведении.

as можно использовать с nullable-типом. Это позволяет результату быть null, но при неудачном приведении всё равно выбрасывается ClassCastException. Поэтому as? — более безопасный вариант:

fun main() {
    val config: Map<String, Any?> = mapOf(
        "username" to "kodee",
        "alias" to null,
        "loginAttempts" to 3
    )

    // Unsafely casts to a nullable String
    val username: String? = config["username"] as String?
    println("Username: $username")
    // Username: kodee

    // Unsafely casts a null value to a nullable String
    val alias: String? = config["alias"] as String?
    println("Alias: $alias")
    // Alias: null

    // Fails to cast to nullable String and throws ClassCastException
    // val unsafeAttempts: String? = config["loginAttempts"] as String?
    // println("Login attempts (unsafe): $unsafeAttempts")
    // Exception in thread "main" java.lang.ClassCastException

    // Fails to cast to nullable String and returns null
    val safeAttempts: String? = config["loginAttempts"] as? String
    println("Login attempts (safe): $safeAttempts")
    // Login attempts (safe): null
}

Приведение к супертипу и подтипу

В Kotlin можно приводить объекты к супертипам и подтипам.

Приведение объекта к экземпляру его суперкласса называется приведением к супертипу. Для этого не нужны специальный синтаксис или операторы приведения типов. Например:

interface Animal {
    fun makeSound()
}

class Dog : Animal {
    // Implements behavior for makeSound()
    override fun makeSound() {
        println("Dog says woof!")
    }
}

fun printAnimalInfo(animal: Animal) {
    animal.makeSound()
}

fun main() {
    val dog = Dog()
    // Upcasts Dog instance to Animal
    printAnimalInfo(dog)  
    // Dog says woof!
}

В этом примере при вызове функции printAnimalInfo() с экземпляром Dog компилятор приводит его к Animal, поскольку именно такой тип параметра ожидается. Так как фактический объект по-прежнему является экземпляром Dog, компилятор динамически разрешает функцию makeSound() из класса Dog и выводит "Dog says woof!".

В API Kotlin часто встречается явное приведение к супертипу, когда поведение зависит от абстрактного типа. Это также распространено в Jetpack Compose и наборах инструментов для пользовательского интерфейса, которые обычно рассматривают все элементы UI как супертипы, а затем работают с определёнными подклассами:

    val textView = TextView(this)
    textView.text = "Hello, View!"

    // Upcasts from TextView to View
    val view: View = textView  

    // Use View functions
    view.setPadding(20, 20, 20, 20)
    // Activity expects a View type
    setContentView(view)

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

interface Animal {
    fun makeSound()
}

class Dog : Animal {
    override fun makeSound() {
        println("Dog says woof!")
    }

    fun bark() {
        println("BARK!")
    }
}

fun main() {
    // Creates animal as a Dog instance with Animal
    // type
    val animal: Animal = Dog()
    
    // Safely downcasts animal to Dog type
    val dog: Dog? = animal as? Dog

    // Uses a safe call to call bark() if dog isn't null
    dog?.bark()
    // "BARK!"
}

В этом примере переменная animal объявлена с типом Animal, но хранит экземпляр Dog. Код безопасно приводит animal к типу Dog и использует безопасный вызов (?.), чтобы обратиться к функции bark().

Приведение к подтипу используется при десериализации базового класса в конкретный подтип. Оно также часто требуется при работе с библиотеками Java, возвращающими объекты супертипа, которые в Kotlin может понадобиться привести к подтипу.

9 июля 2026 г.
МассивыПсевдонимы типов

© 2010–2026 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