«instanceof» в Kotlin: почему это не Java, и как это делает код чище
Если вы пришли в Kotlin из мира Java, вы наверняка привыкли к ключевому слову instanceof. Оно помогает узнать, является ли объект экземпляром определённого класса. Но в Kotlin этого оператора нет. И это не баг, а фича.
Kotlin предлагает гораздо более элегантный, безопасный и лаконичный способ проверить тип объекта — оператор is. Он не только отвечает на вопрос «кто ты?», но и автоматически приводит объект к нужному типу. Это называется умным приведением (smart cast), и это одна из тех фишек, которые делают код на Kotlin таким приятным.
В этом руководстве мы разберём:
- Как использовать оператор
isвместоinstanceof; - Что такое умное приведение и как оно работает;
- Как красиво обрабатывать несколько типов с помощью
when; - Чем отличается безопасное приведение
as?от обычногоas; - И, конечно, реальные примеры кода.
Поехали!
1. Оператор is — прямой аналог instanceof
В Java вы бы написали:
if (obj instanceof String) {
String str = (String) obj;
System.out.println(str.length());
}
В Kotlin то же самое выглядит так:
if (obj is String) {
println(obj.length) // Умное приведение! obj уже String
}
Обратите внимание: после проверки obj is String вам не нужно явно приводить obj к типу String. Компилятор Kotlin сам знает, что внутри блока if переменная obj гарантированно является строкой. Это и есть умное приведение (smart cast).
Как это работает под капотом?
Компилятор анализирует поток выполнения. Если он видит проверку is и не находит возможности изменить тип переменной между проверкой и использованием (например, в многопоточном коде или при изменении var), он автоматически приводит тип. Для val это работает всегда, для var — только если переменная не может быть изменена между проверкой и использованием.
2. Отрицание: !is
Хотите проверить, что объект не относится к типу? Используйте !is:
if (obj !is String) {
println("Это не строка")
} else {
// Здесь obj автоматически стал String
println("Длина строки: ${obj.length}")
}
Удобно и читаемо, правда?
3. Множественные проверки с when
А теперь представьте, что у вас есть объект, который может быть одним из нескольких типов. В Java вы бы написали цепочку if-else if с instanceof. В Kotlin есть более элегантный инструмент — конструкция when.
fun describe(obj: Any): String {
return when (obj) {
is String -> "Строка длиной ${obj.length}"
is Int -> "Целое число: $obj"
is List<*> -> "Список из ${obj.size} элементов"
is Map<*, *> -> "Карта с ${obj.size} записями"
else -> "Неизвестный тип"
}
}
fun main() {
println(describe("Привет")) // Строка длиной 6
println(describe(42)) // Целое число: 42
println(describe(listOf(1,2)))// Список из 2 элементов
}
Обратите внимание: внутри каждой ветки when умное приведение тоже работает! В ветке is String переменная obj уже считается строкой, и вы можете вызывать obj.length без дополнительного приведения.
Это намного компактнее и безопаснее, чем классический switch или цепочка if.
4. Безопасное приведение: as?
Иногда вам нужно не просто проверить тип, а сразу получить значение определённого типа. В Java вы бы сделали (String) obj, но это может выбросить ClassCastException. В Kotlin есть два оператора приведения:
as— обычное приведение (выбросит исключение, если тип не совпадает);as?— безопасное приведение (вернётnull, если тип не совпадает).
Пример:
val obj: Any = "Kotlin"
// Обычное приведение — рискованно
val str1: String = obj as String // Ок
// val num: Int = obj as Int // ClassCastException!
// Безопасное приведение
val str2: String? = obj as? String // "Kotlin"
val num2: Int? = obj as? Int // null (без исключения)
println(num2) // null
as? идеально подходит, когда вы не уверены в типе и хотите избежать краша приложения. В сочетании с элвис-оператором ?: это даёт очень чистый код:
val length = (obj as? String)?.length ?: 0
println(length) // 6, если obj — строка, иначе 0
5. Проверка типа с generics: осторожно, стирание!
Важный момент: в Kotlin, как и в Java, информация об обобщённых типах (generics) стирается во время выполнения. Это значит, что вы не можете напрямую проверить, является ли объект List<String> или List<Int>.
val list: Any = listOf("a", "b")
if (list is List<*>) { // Это работает — проверяем, что это список
println("Это список")
}
// А вот так — НЕ РАБОТАЕТ:
// if (list is List) { // Ошибка компиляции!
// println("Это список строк")
// }
Почему? Потому что во время выполнения стирается информация о типе элементов. Для обхода этого ограничения используйте inline-функции с reified типами. Но это уже тема для отдельной статьи.
6. Когда использовать is, а когда as?
Простое правило:
- Используйте
is, когда вам нужно проверить тип и, возможно, выполнить разные действия. - Используйте
as?, когда вам нужно получить значение определённого типа, и вы готовы получитьnullв случае неудачи. - Избегайте
asбез крайней необходимости — это источникClassCastException.
На практике 90% случаев покрываются связкой is + умное приведение. Безопасное приведение as?