Null-safety: nullable-типы и оператор ?.

Знакомимся с фирменной защитой Kotlin от «ошибки на миллиард долларов».

Null-safety — встроенная в систему типов Kotlin защита, разделяющая типы на те, что могут содержать null (nullable), и те, что не могут.

Создатель null Тони Хоар назвал его «ошибкой на миллиард долларов»: обращение к null рушит программы. Kotlin решает проблему на уровне компилятора, не давая случайно вызвать метод у потенциально пустого значения.

Nullable и non-null типы

Обычный тип, например String, не может быть null. Чтобы разрешить null, к типу добавляют знак вопроса: String?.

fun main() {
    val name: String = "Аня"
    // val bad: String = null   // ОШИБКА: тип не nullable
    val maybe: String? = null   // так можно
    println(name)
    println(maybe)
}

Вывод:

Аня
null

Безопасный вызов ?.

У nullable-значения нельзя просто вызвать метод — компилятор не пропустит. Оператор ?. вызывает метод, только если значение не null; иначе всё выражение возвращает null.

fun main() {
    val name: String? = null
    println(name?.length)        // вернёт null, не упадёт

    val city: String? = "Москва"
    println(city?.uppercase())
}

Вывод:

null
МОСКВА

Цепочки безопасных вызовов

Операторы ?. можно соединять в цепочку. Если любое звено окажется null, вся цепочка вернёт null, не выбросив исключение.

data class Address(val city: String?)
data class User(val address: Address?)

fun main() {
    val user: User? = User(Address(null))
    println(user?.address?.city?.length)
}

Вывод:

null

Как работает под капотом

Знак ? — это не магия времени выполнения, а информация для компилятора. Он отслеживает, какие переменные могут быть null, и отказывается компилировать код, где у них без проверки вызывают методы. В байт-коде String и String? — это один и тот же тип; разница существует только на этапе компиляции и помогает поймать ошибку до запуска программы.

Частые ошибки

  • Делать всё nullable «на всякий случай». Знак ? ставьте, только если значение действительно может отсутствовать.
  • Пытаться вызвать метод напрямую у String?. Компилятор потребует ?. или проверку на null.
  • Забывать, что ?. возвращает null. Результат тоже nullable, и его, возможно, придётся обработать дальше.

Итог

  • Типы делятся на non-null (String) и nullable (String?).
  • Безопасный вызов ?. срабатывает только для не-null значения, иначе возвращает null.
  • Безопасные вызовы можно объединять в цепочку.
  • Проверки работают на этапе компиляции и предотвращают NullPointerException.
Проверьте себя
1. Как объявить переменную типа String, которая может быть null?
Aval s: String = null
Bval s: String? = null
Cval s: null String
Dval s: Nullable<String>
2. Что вернёт name?.length, если name равно null?
A0
BВыбросит NullPointerException
Cnull
DПустую строку
3. На каком этапе Kotlin проверяет null-safety?
AВо время выполнения программы
BНа этапе компиляции
CПри установке JVM
DНе проверяет вообще