Null-безопасность. Общие сведения. Символ ?. Операторы ?. и ?:
Содержание
- 1. Понятие null-значения. Элементы, получающие null-значение
- 2. Объявление переменных, поддерживающих null-значение (nullable-переменные). Символ ?
- 3. Обеспечение null-безопасности в языке Kotlin
- 4. Оператор безопасного вызова ?. . Пример
- 5. Оператор присваивания значения null по умолчанию ?: (Элвис)
- 6. Проверка на null. Оператор !!
- 7. Функция let
- Связанные темы
Поиск на других ресурсах:
1. Понятие null-значения. Элементы, получающие null-значение
В современных языках программирования (в том числе Java, C# и т.д.) возникают ситуации, когда величина не содержит никакого значения. Термины null-значение и нулевое значение – это разные вещи.
В языке Kotlin null значение означает, что для переменных, которые объявлены как val или var, не существует значения на данный момент. Исходя из этого контекста, элементы языка Kotlin делятся на два вида:
- nullable – это элементы, которые могут принимать null-значение;
- non-nullable – элементы, которые не могут принимать null-значение.
При написании программ важно правильное использование элементов, поддерживающих null-значения во избежание ошибок. В других языках программирования (в частности Java) неправильное использование nullable-элементов приводит к возникновению ошибок на этапе выполнения, что очень нежелательно. Как следствие, генерируется исключение (в Java это исключение java.lang.NullPointerException), которое является следствием того, что компилятор не смог обнаружить ошибку во время компиляции программы.
В языке Kotlin система типов организована так, что ошибки с использованием nullable-типов обнаруживаются уже на этапе компиляции. То есть ошибку сразу можно определить и исправить, иначе программный код не будет скомпилирован. Это существенное преимущество языка Kotlin.
⇑
2. Объявление переменных, поддерживающих null-значение (nullable-переменные). Символ ?
В отличие от языка Java (а также других языков), язык Kotlin дает возможность программисту напрямую указывать типы, поддерживающие null-значение. Это осуществляется с помощью добавления символа ? (знак вопроса) к названию типа
Type?
где
- Type – любой базовый тип языка Kotlin (Int, Double, Byte, Short, UByte, Char, String и другие).
Если переменная объявлена как nullable-переменная, то ей можно присваивать значение null.
Пример.
В примере демонстрируется объявление и использование nullable-переменных разных типов.
fun main(args: Array<String>) { var i1 : Int? = null // nullable-переменная типа Int val d1 : Double? = null // nullable-переменная типа Double val s1 : String? = null // nullable-переменная типа String var b1 : Boolean? = null // nullable-переменная типа Boolean var c1 : Char? = null // nullable-переменная типа Char println(i1) println(d1) println(s1) println(b1) // nullable-переменным, которые объявлены как var, // можно присваивать и другие значения i1 = 25; println(i1); b1 = true; println(b1); c1 = '+'; println(c1); }
Результат
null null 25 true +
⇑
3. Обеспечение null-безопасности в языке Kotlin
Если в программе объявляется значение типа с поддержкой null, то на этапе компиляции будет производиться проверка на null-допустимые значения. Таким образом, круг возможных операций сузится. Если операция не обеспечивает null-безопасность, то будет выведено сообщение об ошибке. Ниже приведены возможные случаи таких ошибок и попытки их устранения.
⇑
3.1. Возврат из функции результата, который может быть null
К примеру, в следующем коде будет возникать ошибка компиляции:
// Функция, возвращающая Int из null-строки fun GetLenString(str : String?) : Int { return str.length // здесь ошибка }
При попытке скомпилировать данную функцию будет выведена ошибка
Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?
Действительно, как можно вернуть длину строки типа String?, которая может быть равна null, то есть не существовать.
Чтобы исправить ситуацию, нужно в тело функции GetLenString() ввести код проверки на null-значение вроде
// Функция, возвращающая Int из null-строки fun GetLenString(str : String?) : Int { if (str!=null) // проверка на null return str.length else return 0 }
⇑
3.2. Присваивание nullable-значения переменной, не поддерживающей null
Если объявлена переменная, не поддерживающая null-значение, то попытка присвоения этой переменной значения nullable-переменной вызовет ошибку компиляции.
Например, в следующем коде
fun main(args: Array<String>) { // 1. Переменная, поддерживающая null var X : Double? = 23.8 // 2. Переменная, не поддерживающая null var Y : Double = 33.1 // 3. Попытка присваивания Y = X // ошибка }
при попытке компиляции в строке присваивания
Y = X
компилятор выдаст ошибку
Type mismatch: inferred type is Double? but Double was expected
Чтобы компилятор не выдавал ошибку, нужно реализовать проверку на null, например, да
fun main(args: Array<String>) { // 1. Переменная, поддерживающая null var X : Double? = 23.8 // 2. Переменная, не поддерживающая null var Y : Double = 33.1 // 3. Попытка присваивания, // проверка на null if (X!=null) Y = X // работает, ошибки нету else Y = 0.0 }
⇑
3.3. Передача null-параметра в функцию, в которой объявлен параметр, не поддерживающий null-значение
Если объявляется функция, получающая параметр, не поддерживающий null-значение, то попытка передачи nullable-значения в эту функцию завершится неудачей.
К примеру, в нижеприведенном коде
// Функция, получающая значение, которое не поддерживает тип null fun ShowInteger(num : Int) : Unit { println("num = " + num) } fun main(args: Array<String>) { var num1:Int = 10 // не nullable-тип var num2:Int? = 20 // nullable-тип ShowInteger(num1); // работает ShowInteger(num2) // здесь ошибка }
компилятор в строке
ShowInteger(num2)
выдаст ошибку
Type mismatch: inferred type is Int? but Int was expected
Для устранения ошибки нужно, чтобы функция ShowInteger() получала nullable-тип Int?. В результате код функции может быть примерно таким
// Функция, получающая значение, которое не поддерживает тип null fun ShowInteger(num : Int?) : Unit { // Проверка на null if (num!=null) println("num = " + num) }
⇑
4. Оператор безопасного вызова ?.. Пример
С помощью оператора безопасного вызова ?. можно реализовать проверку на null и метод вызова. В наиболее общем случае, использование оператора? выглядит следующим образом
obj?.MethodName()
здесь
- obj – имя переменной (объекта);
- MethodName() – имя функции (метода), который вызывается из экземпляра (объекта) obj. Вызов MethodName() может быть с передачей ему параметров.
Код вызова obj?.MethodName() конвертируется компилятором в код, который содержит проверку на null
if (obj!=null) obj.MethodName() else null
На рисунке 1 показана работа оператора ?. .
Рисунок 1. Оператор ?. .
Пример.
В примере используется модифицированная функция GetLengthString(), использующая оператор ?. .
// Функция, возвращающая nullable-значение String? fun GetLengthString(s : String?) : Int? { return s?.length // оператор ?. } fun main(args: Array<String>) { // Использование оператора ?. для получения длины null-строки var s : String? = "abcde" println(GetLengthString(s)) s = null println(GetLengthString(s)) }
Результат
5 null
⇑
5. Оператор присваивания значения null по умолчанию ?: (Элвис)
Оператор ?: реализует проверку значения на null на основе двух операндов. Этот оператор еще называется оператором объединения по null (null coalescing operator). В наиболее общем случае использование оператора ?: имеет вид
op1 ?: op2
здесь op1, op2 – некоторые операнды, принимающие участие в получении результата. Результат операции формируется исходя из рисунка 2.
Рисунок 2. Оператор ?:
Пример.
fun main(args: Array<String>) { // Использование оператора ?: // 1. Для типа String var s1 : String? = "abcd" var s2 : String = s1 ?: "" // s2 = "abcd" println(s2) s1 = null s2 = s1 ?: "null value" // s2 = "null value" println(s2) // 2. Для типа Int var i1 : Int? = 25 var i2 : Int = i1 ?: 0 // i2 = 25 println(i2) i1 = null i2 = i1 ?: 0 // i2 = 0 println(i2) }
Результат
abcd null value 25 0
⇑
6. Проверка на null. Оператор !!
С помощью оператора !! можно осуществить приведение к типу, не поддерживающему значение null. Если приводится самое значение null, то Kotlin сгенерирует исключительную ситуацию. Использование оператора !! выглядит следующим образом
obj!!
здесь obj – некоторая переменная (объект).
Работа оператора !! изображена на рисунке 3.
Рисунок 3. Оператор !!
Пример 1. Генерируется исключение для типа String.
fun main(args: Array<String>) { // Оператор !! // Использование для типа String var s1 : String? = "abcd" var s2 : String = s1!! // s2 = "abcd" println(s2) s1 = null s2 = s1!! // исключение NullPointerException }
Результат
abcd Exception in thread "main" kotlin.KotlinNullPointerException at TrainKt.main(train.kt:16)
Пример 2. Генерируется исключение для типа Int.
fun main(args: Array<String>) { // Оператор !! // Использование для типа Int var i1 : Int? = 25 var i2 : Int i2 = i1!! // i2 = 25 println(i2) i1 = null i2 = i1!! // генерируется исключение NullPointerException println(i2) }
Результат
25 Exception in thread "main" kotlin.KotlinNullPointerException at TrainKt.main(train.kt:13)
⇑
7. Функция let
Функция let применяется для лямбда-выражений, допускающих значение null. Данная функция в сочетании с оператором ?. используется для удобной записи nullable выражений.
Общая форма использования функции выглядит следующим образом
obj2 = obj1?.let { // код лямбда выражения // используется it // ... }
здесь
- obj1, obj2 – переменные (объекты), имеющие nullable-тип.
На рисунке 4 изображена работа функции let.
Рисунок 4. Функция let. Безопасный вызов лямбда-выражения
Как видно из рисунка, если obj!=null, то выполняется лямбда-выражение. Если obj==null, то ничего не происходит и лямбда-выражение не выполняется.
С увеличением размера вычисляемого выражения эффект от использования функции let существенно возрастает.
Пример.
fun main(args: Array<String>) { // Функция let в сочетании с ?. // 1. Для типа String // 1.1. Объявлены nullable-переменные var s1 : String? var s2 : String? // 1.2. Не null-значение s1 = "abcd" s2 = s1?.let { // Лямбда-выражение it.reversed() // Реверсирование строки } println(s2) // "dcba" // 1.3. null-значение s1 = null s2 = s1?.let { it.reversed() } println(s2) // null // 2. Использование с типом Double // 2.1. Объявить две nullable-переменные var d1 : Double? var d2 : Double? // 2.2. Не null-значение d1 = 2.33 d2 = d1?.let { // умножить на 3 it * 3 } println(d2) // 6.99 // 2.3. null-значение d1 = null d2 = d1?.let { it * 3 } println(d2) // null }
Результат
dcba null 6.99 null
⇑
Связанные темы
⇑