--- title: Операторы null-безопасности description: Безопасный вызов ?., elvis ?:, as?, !! и функции let, requireNotNull tags: - kotlin - null-safety --- Четыре оператора и несколько функций стандартной библиотеки для работы с nullable-типами. ## Безопасный вызов - ?. Вызывает член, если значение не null, иначе возвращает null: ```kotlin val s: String? = null println(s?.length) // null println(s?.uppercase()) // null val t: String? = "text" println(t?.length) // 4 ``` Результат `?.` всегда nullable - даже если сам член возвращает non-null тип. ### Цепочки ```kotlin class Address(val city: String?) class User(val address: Address?) val user: User? = null val city: String? = user?.address?.city // null, если оборвалось где угодно ``` Цепочка прерывается на первом null, остальные вызовы не выполняются. Это «безопасная навигация» - в Java пришлось бы писать три вложенных `if`. ### С присваиванием ```kotlin class Box { var value: Int = 0 } val box: Box? = null box?.value = 42 // ничего не произойдёт, если box == null ``` ## Elvis - ?: Возвращает левую часть, если она не null, иначе правую: ```kotlin val name: String? = null println(name ?: "анонимный") // анонимный val len = name?.length ?: 0 // 0 ``` Назван так из-за схожести `?:` с причёской Элвиса Пресли. Правая часть вычисляется **только** при null (ленивость): ```kotlin val value = cached ?: expensiveCompute() // вызов только при необходимости ``` ### Для раннего выхода Справа может стоять `return` или `throw` - они имеют тип [Nothing](../Базовые%20типы%20ООП/Any,%20Unit,%20Nothing.md), поэтому подходят под любой тип: ```kotlin fun process(input: String?): Int { val text = input ?: return -1 // выход из функции val n = text.toIntOrNull() ?: error("не число") // исключение return n * 2 } ``` Это идиома: вместо вложенных проверок - плоская последовательность «достали или вышли». ## Безопасное приведение - as? Возвращает null вместо `ClassCastException`: ```kotlin val value: Any = "text" val n: Int? = value as? Int // null val s: String? = value as? String // "text" val safe = value as? Int ?: 0 // 0 — частая связка с elvis ``` Обычный `as` при несовпадении типов падает: ```kotlin // val bad = value as Int // ClassCastException ``` ## Утверждение non-null - !! Превращает `T?` в `T`, бросая NPE при null: ```kotlin val s: String? = "text" println(s!!.length) // 4 val n: String? = null // println(n!!.length) // NullPointerException ``` Применять только когда null означает ошибку программиста, и падение предпочтительнее тихого продолжения. В остальных случаях есть `?:`, `?.` или проверка. ## let для nullable Выполняет блок, только если значение не null: ```kotlin val name: String? = "Kotlin" name?.let { println(it.length) // внутри it — уже String, не String? } val upper = name?.let { it.uppercase() } ?: "ПУСТО" ``` Полезно, когда нужно несколько операций и нежелательно повторять `?.`: ```kotlin user?.let { log(it.name) send(it.email) it.lastSeen = now() } ``` Подробнее про `let`, `run`, `also`, `apply`: [Scope-функции](../../Функциональный%20Kotlin/Scope-функции.md) ## Функции проверки ```kotlin val value: String? = getValue() val a = requireNotNull(value) // IllegalArgumentException val b = requireNotNull(value) { "нет значения" } val c = checkNotNull(value) // IllegalStateException ``` Обе возвращают non-null значение, то есть заменяют `!!` с внятным сообщением. Разница - в типе исключения: `require*` для проверки аргументов, `check*` для проверки состояния. Подробнее: [Проверки и контракты](../../Исключения/Проверки%20и%20контракты.md) ## Полезные nullable-функции stdlib ```kotlin val s: String? = null println(s.isNullOrEmpty()) // true — работает на nullable-получателе println(s.isNullOrBlank()) // true println(s.orEmpty()) // "" — строка вместо null val list: List? = null println(list.orEmpty()) // [] println(list?.size ?: 0) // 0 println("42".toIntOrNull()) // 42 println("abc".toIntOrNull()) // null — вместо исключения ``` Функции с суффиксом `OrNull` есть у многих операций: `firstOrNull()`, `singleOrNull()`, `toIntOrNull()`, `getOrNull(i)`. ## Сводка | Оператор | Результат при null | Когда применять | |---|---|---| | `?.` | `null` | навигация по цепочке | | `?:` | правая часть | значение по умолчанию, ранний выход | | `as?` | `null` | неуверенное приведение типа | | `!!` | NPE | null невозможен по инварианту | | `?.let {}` | блок не выполняется | несколько действий со значением | > [!info] Отличия от Java > | Java | Kotlin | > |---|---| > | `if (x != null) x.foo()` | `x?.foo()` | > | `x != null ? x : def` | `x ?: def` | > | `x instanceof T ? (T) x : null` | `x as? T` | > | `Objects.requireNonNull(x)` | `requireNotNull(x)` или `x!!` | > | `Optional.ofNullable(x).map(f).orElse(d)` | `x?.let(f) ?: d` | > | три вложенных `if` для цепочки | `a?.b?.c` | > > Java-аналог `Optional` требует оборачивания и разворачивания, работает только как возвращаемое значение и создаёт объект. Операторы Kotlin компилируются в обычные проверки `ifnull` в байт-коде - накладных расходов нет. > > Сравнить: [Приведение ссылочных типов в Java](../../../Программирование%20на%20Java/Синтаксис%20и%20типы/Работа%20с%20данными%20и%20типами/Приведение%20ссылочных%20типов.md) > [!warning] Подводные камни > **`?.` возвращает nullable, цепочка «заражает» весь результат:** > ```kotlin > val list: List? = listOf("a") > val size: Int? = list?.size // Int?, не Int > val sure: Int = list?.size ?: 0 // нужен elvis > ``` > > **Приоритет `?:` ниже арифметики** - без скобок получится не то: > ```kotlin > val a: Int? = null > println((a ?: 0) + 1) // 1 > // println(a ?: 0 + 1) // это a ?: (0+1), то есть 1 — здесь совпало, > // // но в сложных выражениях расхождение будет > ``` > > **`as?` не спасает от null, только от неверного типа:** > ```kotlin > val x: Any? = null > val s: String? = x as? String // null — потому что x был null > ``` > > **`!!` в цепочке скрывает, что именно упало.** `a!!.b!!.c!!` даёт NPE без указания звена. Либо проверяйте по шагам, либо используйте `requireNotNull` с сообщением. > > **`?.let` возвращает null, если получатель null** - а не то, что вернул блок: > ```kotlin > val s: String? = null > val r: Int? = s?.let { 42 } // null, блок не выполнялся > ``` > > **Безопасный вызов на функции-расширении с nullable-получателем работает иначе.** `s.isNullOrEmpty()` вызывается без `?.`, потому что расширение объявлено для `String?` и само обрабатывает null внутри. Дополнительно: [Scope-функции](../../Функциональный%20Kotlin/Scope-функции.md) Далее: [Умные приведения](Умные%20приведения.md)