Smart cast to 'Type' is impossible: как исправить ошибку с изменяемыми свойствами в Kotlin
Почему возникает ошибка Smart cast?
Проблема в том, что компилятор Kotlin не может гарантировать, что между проверкой типа и использованием переменной её значение не изменится. Для var (mutable property) это возможно, например, в многопоточной среде или если свойство переопределяется. Для val (immutable) такой проблемы нет, так как значение не может измениться.
Основные решения
1. Использование локальной неизменяемой переменной
Самый простой и рекомендуемый способ — сохранить значение в локальную val внутри блока проверки:
var value: Any = "Hello, Kotlin!"
if (value is String) {
val str = value as String // явное приведение
println(str.length)
}
Или ещё проще — использовать умное приведение (smart cast) через let:
var value: Any = "Hello, Kotlin!"
(value as? String)?.let { str ->
println(str.length)
}
2. Использование оператора as? (safe cast)
Оператор as? возвращает null, если приведение невозможно, что безопасно:
var value: Any = "Hello, Kotlin!"
val str = value as? String
if (str != null) {
println(str.length)
}
3. Использование when с умным приведением
Конструкция when также поддерживает умное приведение, если переменная не изменяется внутри:
var value: Any = "Hello, Kotlin!"
when (value) {
is String -> println(value.length)
is Int -> println(value + 1)
else -> println("Unknown type")
}
Обратите внимание: если вы измените value внутри when, компилятор снова выдаст ошибку.
4. Использование synchronized или потокобезопасных коллекций
Если переменная изменяется из разных потоков, можно синхронизировать доступ:
var value: Any = "Hello, Kotlin!"
synchronized(this) {
if (value is String) {
println(value.length) // теперь безопасно
}
}
Почему это важно?
Kotlin — язык с безопасной типизацией. Smart cast — это мощная фича, но компилятор защищает вас от ошибок, которые могут возникнуть при изменении переменной между проверкой и использованием. Всегда используйте val по умолчанию, а если нужен var, применяйте один из описанных выше подходов.
Рекомендую второй вариант с let и as? — он самый лаконичный и безопасный.
Альтернативный подход: использование делегированных свойств
Если вы часто сталкиваетесь с такой проблемой, можно использовать делегат lazy или observable, но это не решит корень проблемы. Лучше переосмыслить дизайн: задайте себе вопрос — действительно ли переменная должна быть изменяемой?
Пример с by lazy:
val value: Any by lazy { "Hello, Kotlin!" }
if (value is String) {
println(value.length) // работает, так как value — val
}
Использование requireNotNull:
Если вы уверены, что значение не null и имеет нужный тип, можно использовать:
var value: Any? = "Hello"
requireNotNull(value)
if (value is String) {
println(value.length)
}
Когда это может быть полезно?
В некоторых случаях, например, при работе с Android View или компонентами, которые инициализируются позже, лучше использовать lateinit или делегаты. Но помните: если свойство может измениться после проверки, всегда делайте локальную копию.