Операторы ?:, !! и умные приведения

Учимся подставлять значения по умолчанию и понимать, когда !! опасен.

Элвис-оператор ?: возвращает левое значение, если оно не null, иначе — правое (значение по умолчанию).

Оператор ?. мы освоили, но что делать, когда вместо null нужно подставить конкретное значение? Здесь на сцену выходит элвис-оператор и его опасный сосед — !!.

Элвис-оператор ?:

Название шуточное: символы ?: напоминают причёску и глаза Элвиса. Оператор задаёт запасное значение на случай null.

fun main() {
    val name: String? = null
    val display = name ?: "Гость"
    println(display)

    val len = name?.length ?: 0
    println(len)
}

Вывод:

Гость
0

Оператор !! — на свой страх и риск

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

fun main() {
    val name: String? = "Аня"
    println(name!!.length)   // сработает: name не null
    // если бы name был null — NullPointerException
}

Вывод:

3

Умное приведение типов (smart cast)

После явной проверки на null компилятор сам «понимает», что внутри ветки значение точно не null, и разрешает обращаться к нему как к обычному типу — без ?..

fun describe(text: String?) {
    if (text != null) {
        // здесь text уже String, не String?
        println("Длина: ${text.length}")
    } else {
        println("Пусто")
    }
}

fun main() {
    describe("Котлин")
    describe(null)
}

Вывод:

Длина: 6
Пусто

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

Smart cast возможен, когда компилятор может гарантировать, что между проверкой и использованием значение не изменилось. Для val это всегда так. А вот для изменяемого var (особенно свойства класса) smart cast может не сработать: значение теоретически могло поменяться в другом потоке. В таких случаях значение сохраняют в локальный val.

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

  • Злоупотреблять !!. Каждый !! — потенциальный NullPointerException; почти всегда лучше ?: или проверка.
  • Дублировать значение по умолчанию. name ?: "Гость" короче и яснее, чем if.
  • Ждать smart cast для var-свойства. Сохраните его в локальную val-переменную.

Итог

  • ?: подставляет значение по умолчанию вместо null.
  • !! отключает защиту и кидает NullPointerException при null — применяйте осторожно.
  • После проверки if (x != null) срабатывает smart cast.
  • Для надёжного smart cast используйте val.
Проверьте себя
1. Что делает элвис-оператор в выражении name ?: "Гость"?
AВсегда возвращает "Гость"
BВозвращает name, если он не null, иначе "Гость"
CВыбрасывает исключение, если name равно null
DСравнивает name и "Гость"
2. Чем опасен оператор !!?
AОн замедляет программу
BОн бросит NullPointerException, если значение всё же окажется null
CОн всегда возвращает null
DОн меняет тип на nullable
3. Что такое smart cast?
AБыстрое преобразование чисел
BКомпилятор сам приводит nullable к non-null после проверки на null
CАвтоматическое округление
DКеширование объектов