type | layout | category | title | url |
---|---|---|---|---|
doc |
reference |
Syntax |
Null безопасность |
Система типов в языке Kotlin нацелена на то, чтобы искоренить опасность обращения к null значениям, более известную как "Ошибка на миллион".
Самым распространённым подводным камнем многих языков программирования, в том числе Java, является попытка произвести доступ к null значению.
Это приводит к ошибке. В Java такая ошибка называется NullPointerException
(сокр. "NPE").
Kotlin призван исключить ошибки подобного рода из нашего кода. NPE могу возникать только в случае:
- Явного указания
throw NullPointerException()
- Использования оператора
!!
(описано ниже) - Эту ошибку вызвал внешний Java-код
- Есть какое-то несоответствие при инициализации данных (в конструкторе использована ссылка this на данные, которые не были ещё проинициализированы)
Система типов Kotlin различает ссылки на те, которые могут иметь значение null (nullable ссылки) и те, которые таковыми быть не могут (non-null ссылки).
К примеру, переменная часто используемого типа String
не может быть null:
var a: String = "abc"
a = null // ошибка компиляции
Для того, чтобы разрешить null значение, мы можем объявить эту строковую переменную как String?
:
var b: String? = "abc"
b = null // ok
Теперь, при вызове метода с использованием переменной a
, исключены какие-либо NPE. Вы спокойно можете писать:
val l = a.length
Но в случае, если вы захотите получить доступ к значению b
, это будет небезопасно. Компилятор предупредит об ошибке:
val l = b.length // ошибка: переменная `b` может быть null
Но нам по-прежнему надо получить доступ к этому свойству/значению, так? Есть несколько способов этого достичь.
Первый способ. Вы можете явно проверить b
на null значение и обработать два варианта по отдельности:
val l = if (b != null) b.length else -1
Компилятор отслеживает информацию о проведённой вами проверке и позволяет вызывать length
внутри блока if.
Также поддерживаются более сложные конструкции:
if (b != null && b.length > 0) {
print("String of length ${b.length}")
} else {
print("Empty string")
}
Обратите внимание: это работает только в том случае, если b
является неизменной переменной (ориг.: immutable). Например, если
это локальная переменная, значение которой не изменяется в период между его проверкой и использованием. Также такой переменной может служить val.
В противном случае может так оказаться, что переменная b
изменила своё значение на null после проверки.
Вторым способом является оператор безопасного вызова ?.
:
b?.length
Этот код возвращает b.length
в том, случае, если b
не имеет значение null. Иначе он возвращает null. Типом этого выражения будет Int?
.
Такие безопасные вызовы полезны в цепочках. К примеру, если Bob, Employee (работник), может быть прикреплён (или нет) к отделу Department, и у отдела может быть управляющий, другой Employee. Для того, чтобы обратиться к имени этого управляющего (если такой есть), напишем:
bob?.department?.head?.name
Такая цепочка вернёт null в случае, если одно из свойств имеет значение null.
Для проведения каких-либо операций исключительно над non-null значениями вы можете использовать let
оператор вместе с оператором безопасного вызова:
val listWithNulls: List<String?> = listOf("A", null)
for (item in listWithNulls) {
item?.let { println(it) } // выводит A и игнорирует null
}
Если у нас есть nullable ссылка r
, мы можем либо провести проверку этой ссылки и использовать её, либо использовать non-null значение x
:
val l: Int = if (b != null) b.length else -1
Аналогом такому if-выражению является элвис-оператор ?:
:
val l = b?.length ?: -1
Если выражение, стоящее слева от Элвис-оператора, не является null, то элвис-оператор его вернёт. В противном случае, в качестве возвращаемого значения послужит то, что стоит справа. Обращаем ваше внимание на то, что часть кода, расположенная справа, выполняется ТОЛЬКО в случае, если слева получается null.
Так как throw и return тоже являются выражениями в Kotlin, их также можно использовать справа от Элвис-оператора. Это может быть крайне полезным для проверки аргументов функции:
fun foo(node: Node): String? {
val parent = node.getParent() ?: return null
val name = node.getName() ?: throw IllegalArgumentException("name expected")
// ...
}
Для любителей NPE существует ещё один способ. Мы можем написать b!!
и это вернёт нам либо non-null значение b
(в нашем примере вернётся String
), либо выкинет NPE:
val l = b!!.length
В случае, если вам нужен NPE, вы можете заполучить её только путём явного указания.
Обычное приведение типа может вызвать ClassCastException
в случае, если объект имеет другой тип.
Можно использовать безопасное приведение, которое вернёт null, если попытка не удалась:
val aInt: Int? = a as? Int
Если у вас есть коллекция nullable элементов и вы хотите отфильтровать все non-null элементы, используйте функцию filterNotNull
.
val nullableList: List<Int?> = listOf(1, 2, null, 4)
val intList: List<Int> = nullableList.filterNotNull()