Перейти к содержанию
Educora
Средний15 мин4 / 14

Null-безопасность

Как Kotlin защищает от самой известной причины падений приложений: типы с `?`, `?.`, `?:`, `!!` и умные приведения типов.

Проверь себя
В этом уроке ты узнаешь
  • Объяснять, что такое null и чем обычный тип отличается от типа, допускающего null
  • Безопасно обрабатывать null с помощью операторов ?. и ?:
  • Понимать, чем опасен оператор !!
  • Использовать умные приведения типов (!= null, is)

В форме регистрации поле «отчество» может остаться пустым, а пользователь может так и не загрузить фото профиля. В программировании состояние «значения нет» обозначается null. В языках вроде Java обращение к переменной, где лежит null, роняет программу с ошибкой NullPointerException, и годами это было одной из самых частых причин падений Android-приложений. Kotlin решает эту проблему уже на этапе компиляции.

Типы, допускающие null

В Kotlin обычные типы не могут хранить null: если написать val name: String = null, компилятор сообщит об ошибке. Чтобы показать, что переменная может быть пустой, к типу добавляют вопросительный знак: String? означает «строка или null». Так сам код говорит, какие значения могут отсутствовать.

Kotlin
fun main() {
    val name: String = "Aysel"
    var nickname: String? = null
    println(name.length)
    println(nickname)
    nickname = "Ays"
    println(nickname)
}
Ожидаемый результат
5
null
Ays

В этой программе нельзя написать nickname.length — компилятор этого не позволит, ведь nickname может быть null. Kotlin заставляет сначала ответить на вопрос: «что делать, если значения нет?». Для этого есть специальные операторы.

Безопасный вызов ?. и оператор Элвис ?:

Безопасный вызов ?. работает так: если значение есть, метод вызывается; если там null, всё выражение просто становится null, и программа не падает. Оператор Элвис ?: возвращает запасное значение справа, когда левая часть равна null. Своё название он получил потому, что знак ?:, повёрнутый набок, напоминает причёску Элвиса Пресли.

Kotlin
fun main() {
    val city: String? = null
    val country: String? = "Azerbaijan"
    println(city?.length)
    println(country?.length)
    println(city ?: "unknown")
    val len = city?.length ?: 0
    println("Length: $len")
    println(country?.uppercase()?.take(3))
}
Ожидаемый результат
null
10
unknown
Length: 0
AZE
Вызовы ?. можно выстраивать цепочкой; take(3) берёт первые 3 символа.

Значения, допускающие null, часто встречаются в реальном коде. Например, toIntOrNull() превращает строку в число, а если это невозможно, возвращает null вместо ошибки — вместе с оператором Элвис это удобный способ проверять то, что ввёл пользователь:

Kotlin
fun main() {
    val good = "42".toIntOrNull()
    val bad = "forty-two".toIntOrNull()
    println(good)
    println(bad)
    println((bad ?: 0) + 1)
}
Ожидаемый результат
42
null
1

Оператор !! — «я уверен»

Оператор !! говорит компилятору: «Это значение не null, я уверен». Если ты ошибся, программа тут же упадёт с NullPointerException. Иначе говоря, !! добровольно отключает защиту, которую даёт Kotlin.

Kotlin
val code: String? = "AZ1000"
println(code!!.length)    // 6

val empty: String? = null
println(empty!!.length)   // crash: NullPointerException

Умные приведения типов

После проверки if (email != null) внутри блока Kotlin автоматически считает email обычной String — это называется умным приведением типа (smart cast). То же самое происходит при проверке типа оператором is. Any — общий тип, к которому относится любое значение.

Kotlin
fun main() {
    val email: String? = "leyla@example.com"
    if (email != null) {
        println("Email has ${email.length} characters")
    }

    val value: Any = 42
    val text = when (value) {
        is String -> "a string of length ${value.length}"
        is Int -> "an Int, doubled: ${value * 2}"
        else -> "something else"
    }
    println(text)
}
Ожидаемый результат
Email has 17 characters
an Int, doubled: 84
В ветке is Int значение value уже считается Int, поэтому его можно умножить на 2.
ЗаписьНазваниеЧто делает
String?тип, допускающий nullхранит значение или null
s?.lengthбезопасный вызовдаёт null, если s равно null
s ?: "none"оператор Элвисподставляет запасное значение вместо null
s!!.lengthутверждение «не null»при null роняет программу

Главное

  • Обычные типы не хранят null; типы вроде String? — могут.
  • ?. возвращает null вместо падения.
  • ?: (Элвис) подставляет запасное значение, если слева null: name ?: "guest".
  • !! отключает защиту и при null выбрасывает NullPointerException — избегай его.
  • После проверки != null или is компилятор сам уточняет тип (smart cast).

Проверь себя

Вопросов: 10. Каждый правильный ответ приносит XP.

1 / 10
Какая строка скомпилируется?