Как работает система nullable типов в Kotlin?
Система nullable типов в Kotlin — одна из ключевых особенностей языка, направленная на повышение безопасности и предотвращение частой ошибки, известной как NullPointerException (NPE), или "The Billion Dollar Mistake". В отличие от Java, Kotlin делает null частью системы типов и требует явного указания возможности null.
Объявление nullable переменных
В Kotlin переменная по умолчанию не может быть null:
var name: String = "Alex"
name = null // Ошибка компиляции!
Чтобы переменная могла содержать null, нужно явно указать это, добавив ? к типу:
var name: String? = "Alex"
name = null // OK
Тип String? означает, что переменная может содержать либо строку (String), либо null.
Безопасный вызов (?.)
Чтобы обратиться к свойствам или методам nullable-переменной, используется безопасный вызов:
val length: Int? = name?.length
Если name — не null, вернётся длина строки. Если null, результатом будет null, и length тоже станет null.
Оператор !! (not-null assertion)
Если вы уверены, что значение не null, можно использовать !!:
val length = name!!.length
Но если name == null, произойдёт NullPointerException. Используйте !! только в крайнем случае — это принудительное снятие защиты от null.
Оператор Элвиса (?:)
Позволяет указать значение по умолчанию:
val length = name?.length ?: 0
Если name == null, результат будет 0.
Smart Cast (умное приведение типа)
Kotlin умеет автоматически проверять null и делать безопасный cast:
if (name != null) {
println(name.length) // name автоматически cast-ится в String
}
Но если есть побочные эффекты или другие ветки, компилятор может не сработать:
if (name != null && name.length > 3) {
// OK
}
Safe let (idiома с let)
Позволяет выполнять действия, только если значение не null:
name?.let {
println("Длина имени: ${it.length}")
}
Пример с функцией
fun printName(name: String?) {
println(name?.uppercase() ?: "Имя не указано")
}
Nullable-параметры в функциях
fun greet(user: String?) {
if (user != null) {
println("Hello, $user!")
} else {
println("Hello, guest!")
}
}
Типы как Any?, Unit?, Int?
-
Int — не может быть null.
-
Int? — может быть null, это обёртка (boxed Int) под капотом: java.lang.Integer.
Kotlin обеспечивает, чтобы работа с примитивами оставалась производительной, несмотря на nullable.
Пример: сложение nullable значений
fun add(a: Int?, b: Int?): Int {
return (a ?: 0) + (b ?: 0)
}
Пример с ошибкой
val a: String? = null
val b: Int = a.length // Ошибка компиляции — a может быть null
Null в коллекциях
val list: List<String?> = listOf("A", null, "C")
val filtered: List<String> = list.filterNotNull()
filterNotNull() убирает все null из списка.
Тип Unit vs Nothing vs Null
-
Unit — аналог void, но это настоящий тип.
-
Nothing — возвращается функциями, которые не завершаются нормально (например, бросают исключение).
-
Null — тип только для null.
Nullable возвращаемые значения
fun findUser(id: Int): User? {
// Возвращает null, если пользователь не найден
}
Такой подход используется вместо выбрасывания исключений.
Аннотации для Java-интероперабельности
Когда Kotlin работает с Java-кодом, Kotlin не всегда может понять, может ли возвращаемое значение быть null. Используются аннотации:
@Nullable
String findName();
Kotlin интерпретирует это как String?.
Пример с map и nullable
val list = listOf("a", null, "b")
val lengths = list.map { it?.length ?: 0 }
❓Поддержка в стандартной библиотеке
Множество функций адаптированы под работу с nullable:
val result = name?.takeIf { it.length > 3 } ?: "Unknown"
Kotlin делает работу с null безопасной и предсказуемой. Благодаря nullable-типам, большинству NullPointerException можно избежать уже на этапе компиляции.