Работа с null-значениями
#
Васильев Андрей Михайлович, 2024
Версии презентации
Одна из самых распространённых ошибок
#
Рассмотрим следующий код, написанный на Java:
private String getData() { ... }
private void process() {
String data = getData();
System.out.println(data.length().toString());
}
В переменную data
мы записываем результат работы функции getData
Возможными значениями являются:
- Некоторая строка
- Значение
null
В случае null
на строке 2 приложение завершит свою работу с ошибкой
Ошибка NullPointerException в Kotlin
#
В Kotlin возможны только следующие ситуации, когда это исключение может быть
выброшено:
- Явное выбрасывание
NullPointerException
- Некорректный код при использовании оператора
!!
- Неконситентная инициализация объекта
- Результат взаимодействия с Java-кодом
Null-значения в Kotlin
#
Kotlin явно отделяет выражения, которые могут принимать значение null
и те,
которые не могут его принимать
Например следующий код вызовет ошибку компилятора:
var message:String = "Как прекрасен этот мир"
message = null // Ошибка компилятора
Но если мы изменим тип переменной, то код скомпилируется:
var message:String? = "Как непонятен этот мир"
message = null
?
в конце обозначает не отдельный тип, а то, что значение может либо принимать
значение типа, либо null
Давайте использовать везде ?
#
Если есть спокойный способ не думать о null-значении, то зачем этим
заниматься?
Ответ: компилятор kotlin будет требовать от вас доказательства того, что внутри
переменной нет null, чтобы писать код удобно:
val goodMessage:String = "Надёжная строка"
val goodMessageLength = goodMessage.length
val unknownMessage:String? = "Ненадёжная строка"
val unknownMessageLength = unknownMessage.length // Ошибка!
Существует несколько способов работы со выражениями, которые могут принимать
значение null
Явная проверка на null-значение
#
Во-первых можно воспользоваться условным выражением, чтобы обработать оба
возможных состояния выражения
val message:String? = "Странная, но нужная строка"
val length = if (message != null) message.length else -1
В случае отсутствия null-значения компилятор позволит обратиться к свойству
объекта, т.к. в коде явно указано, что в данном пространстве не произойдёт NPE
Можно выполнять и более сложные проверки
val message: String? = "Kotlin"
if (message != null && message.length > 0) {
print("Длина строки ${message.length}")
} else {
print("Строка пустая")
}
Такая проверка будет работать только с неизменяемыми переменными, val
, т.к. в
многопоточном приложении изменение переменной может произойти в любой момент
Безопасные вызовы
#
Вторым способом доступа к свойству nullable переменной - это использование
оператора безопасного вызова ?.
val message = "Kotlin"
val nullMessage: String? = null
println(nullMessage?.length) // Результат выражения - null
println(nullMessage?.length) // Ненужный безопасный вызов
Этот код возвращает nullMessage.length
в том, случае, если nullMessage
не
имеет значение null
. Иначе он возвращает null
. Типом этого выражения будет
Int?
Такие вызовы можно выстраивать в цепочку, когда мы хотим обратиться к сложным
вложенным объектам:
bob?.department?.head?.name
Такая цепочка вернёт null
в случае, если одно из свойств имеет значение null
Стоит ограниченно прибегать к данному подходу
Выполнение нескольких операций
#
Безопасный вызов позволяет нам выполнить только одну операцию над значением,
которое не равно null
Для проведения каких-либо операций исключительно над не-null
значениями вы
можете использовать let
оператор вместе с оператором безопасного вызова.
val listWithNulls: List<String?> = listOf("Kotlin", null)
for (item in listWithNulls) {
item?.let {
val lenghth = it.length
println("$it: $length")
} // выводит Kotlin:6 и игнорирует null
}
Элвис-оператор
#
Если у вас есть nullable
ссылка b
, вы можете либо провести проверку этой ссылки
и использовать её, либо использовать не-null
значение:
val l: Int = if (b != null) b.length else -1
Вместо того чтобы писать полное if-выражение, вы можете использовать
элвис-оператор ?:
Если выражение, стоящее слева от Элвис-оператора, не является null
, то
элвис-оператор его вернёт. В противном случае в качестве возвращаемого значения
послужит то, что стоит справа. Обращаем ваше внимание на то, что часть кода,
расположенная справа, выполняется ТОЛЬКО в случае, если слева получается null
.
Защитные выражения
#
При разработке функции удобно избавляться от неясности данных, переданных в
качестве аргументов в самом начале функции
Мы можем выйти из функции в тот момент, когда становится понятно, что далее
обработка данных уже невозможна
fun foo(node: Node): String {
val parent = node.getParent() ?: return ""
...
}
Если не удалось получить значение из переданного аргумента, функция завершает
свою работу
Такие проверки можно выполнять не только на null
, но и на другие значения
Оператор !!
#
Для любителей NPE существует третий способ: оператор not-null (!!) преобразует
любое значение в non-null тип и выдает исключение, если значение равно null
Рассмотрим следующую переменную:
val message: String? = ...
Вы можете написать message!!
и это вернёт нам либо non-null значение message
(в нашем примере вернётся String), либо выкинет NPE, если message
равно
null
val length = message!!.length
В случае, если вам нужен NPE, вы можете заполучить её только путём явного указания
Использование данного оператора при написании кода в курсе запрещено