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