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
⇑
Споріднені теми
⇑