Что такое 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&lt;Any&gt;) {
if (list is List&lt;String&gt;) {
// Warning: unchecked cast
}
}

Чтобы обойти это ограничение, можно использовать reified-параметры и inline-функции:

inline fun &lt;reified T&gt; isOfType(x: Any): Boolean {
return x is T
}
val result = isOfType&lt;String&gt;("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&lt;Any&gt; = listOf(1, "kotlin", true, "scala")
val strings = items.filter { it is String }.map { it as String }
// безопаснее так:
val strings2 = items.filterIsInstance&lt;String&gt;()

Метод filterIsInstance<T>() — удобная обёртка над is, используется для фильтрации по типу.

Таким образом, is — это мощный инструмент в языке Kotlin, который сочетает безопасность, лаконичность и выразительность. Он особенно полезен в проверках типов, работе с sealed class, и в типобезопасных конструкциях when.