Что такое is и как он используется для проверки типа?
В Kotlin ключевое слово is используется для проверки принадлежности объекта к определённому типу во время выполнения. Оно аналогично оператору instanceof в Java, но обладает более мощным и удобным синтаксисом, особенно благодаря механизму smart cast — автоматическому приведению типа переменной после успешной проверки. Это делает код чище и безопаснее.
Синтаксис и поведение
if (obj is String) {
println(obj.length) // obj автоматически приведён к String внутри этого блока
}
Оператор is возвращает true, если объект является экземпляром указанного типа или его подтипа. Внутри блока if, компилятор автоматически "понимает", что объект имеет этот тип и позволяет обращаться к его членам без явного кастинга (as).
Обратная проверка с !is
Kotlin также поддерживает отрицание через !is:
if (obj !is Int) {
println("obj не Int")
}
Здесь код выполняется, если объект не принадлежит типу Int.
Пример с when
Оператор is часто используется внутри конструкции when, особенно если нужно проверить значение на несколько разных типов:
fun describe(x: Any): String = when (x) {
is Int -> "Целое число: $x"
is String -> "Строка длиной ${x.length}"
is List<\*> -> "Список из ${x.size} элементов"
else -> "Неизвестный тип"
}
Каждая ветка when проверяет принадлежность к определённому типу и при этом автоматически делает smart cast — т.е. в теле ветки x уже является нужным типом.
Условия применения smart cast
Автоматическое приведение типа (smart cast) работает только в тех случаях, когда компилятор уверен, что переменная не может быть изменена после проверки. Поэтому:
-
переменная должна быть val, а не var;
-
переменная должна быть локальной (например, параметром функции или переменной внутри тела метода);
-
переменная не должна быть свойством класса, если у неё нет явно финализированного геттера.
Пример, когда smart cast работает:
fun printLength(obj: Any) {
if (obj is String) {
println(obj.length) // OK: smart cast работает
}
}
Пример, когда smart cast не работает:
fun printLength(obj: Any) {
var temp = obj
if (temp is String) {
println(temp.length) // Ошибка: smart cast невозможен
}
}
Компилятор не может гарантировать, что переменная temp не была изменена между проверкой и использованием.
Пример с вложенными структурами
fun process(data: Any) {
if (data is List<\*>) {
val first = data.firstOrNull()
if (first is String) {
println("Первый элемент — строка длиной ${first.length}")
}
}
}
Можно комбинировать несколько проверок is внутри вложенных условий, получая доступ к безопасно приведённым значениям.
Использование с обобщёнными типами (generics)
Из-за type erasure в JVM, проверка параметризированного типа (List<String>, Map<Int, String>) в runtime невозможна напрямую:
fun check(list: List<Any>) {
if (list is List<String>) {
// Warning: unchecked cast
}
}
Чтобы обойти это ограничение, можно использовать reified-параметры и inline-функции:
inline fun <reified T> isOfType(x: Any): Boolean {
return x is T
}
val result = isOfType<String>("hello") // true
Ключевое слово reified позволяет сохранить информацию о типе во время выполнения, что делает проверку безопасной и корректной.
Применение с sealed class
is идеально сочетается с sealed class, позволяя компилятору заставить вас покрыть все возможные типы:
sealed class Result
data class Success(val data: String) : Result()
data class Error(val message: String) : Result()
fun handle(result: Result): String = when (result) {
is Success -> "Успешно: ${result.data}"
is Error -> "Ошибка: ${result.message}"
}
Если вы добавите в sealed class ещё один тип, компилятор потребует обновить when.
Отличие от приведения типов (as)
Оператор is — это проверка, без приведения. Он возвращает true/false.
Если нужно привести тип — используется as, но он небезопасен:
val s = x as String // если x не String, будет ClassCastException
Для безопасного приведения — as?:
val s = x as? String // вернёт null, если x не String
Однако если вам просто нужно проверить тип и использовать значение — is предпочтительнее:
if (x is String) {
println(x.uppercase()) // безопасно и без кастинга
}
Использование с лямбдами и фильтрацией коллекций
val items: List<Any> = listOf(1, "kotlin", true, "scala")
val strings = items.filter { it is String }.map { it as String }
// безопаснее так:
val strings2 = items.filterIsInstance<String>()
Метод filterIsInstance<T>() — удобная обёртка над is, используется для фильтрации по типу.
Таким образом, is — это мощный инструмент в языке Kotlin, который сочетает безопасность, лаконичность и выразительность. Он особенно полезен в проверках типов, работе с sealed class, и в типобезопасных конструкциях when.