Как работает система 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 можно избежать уже на этапе компиляции.