Полное руководство по обработке пустых ссылок в Kotlin

Обзор

В этой статье мы рассмотрим нулевую безопасность в Котлине.

Любой язык программирования, имеющий концепцию нулевой ссылки, вызывает исключение NullPointerException. Это было названо ошибкой на миллиард долларов. (Вики)

Тип, допускающий и не допускающий значения null

Kotlin стремится устранить риск NullPointerException. Он различает ссылки, допускающие и не допускающие значения NULL, как часть своей системы типов.

В Kotlin по умолчанию все переменные не допускают значения NULL. Мы не можем присвоить переменной значение null, потому что это приведет к ошибке компиляции:

var country: String = "India"
country = null //compilation error

Чтобы определить переменную, допускающую значение NULL, мы должны добавить вопросительный знак (?) к объявлению типа:

var city: String? = "Kolkata"
city = null

Мы можем вызвать метод или получить доступ к свойству переменной, не допускающей значения NULL. Однако в случае переменных, допускающих значение NULL, нам необходимо явно обработать случай NULL. В противном случае он выдаст ошибку компиляции, поскольку Kotlin знает, что переменная содержит пустые ссылки:

val a : String = country.length
val b : String = city.length //compilation error

Давайте посмотрим, как можно безопасно обрабатывать пустые ссылки в Kotlin.

Работа с типами, допускающими значение NULL

Проверка нуля

Мы можем использовать выражение if-else для явной проверки переменных, допускающих значение NULL. Этот параметр работает только в том случае, если переменная неизменна. В зависимости от сложности условий это также может привести к вложенным выражениям.

Давайте посмотрим на пример:

val city: String? = "Kolkata"
return if (city != null) {
    city.length
} else {
    null
}

Оператор безопасного вызова (?.)

В Kotlin есть безопасный оператор вызова (?.) для обработки пустых ссылок. Этот оператор выполняет любое действие только тогда, когда ссылка имеет ненулевое значение. В противном случае возвращается нулевое значение. Оператор безопасного вызова объединяет проверку на null и вызов метода в одном выражении.

Давайте посмотрим, как использовать безопасный оператор вызова:

val country: String? = "India"
assertEquals(5, country?.length)
val city: String? = null
assertNull(city?.length)

Мы также можем использовать безопасный оператор вызова для нескольких цепных вызовов:

val country: Country? = Country(City("Kolkata", "003"))
val code: String? = country?.city?.code
assertEquals("003", code)

Вызовы цепочки возвращают значение null, если какое-либо из свойств имеет значение null:

val country: Country? = Country(null)
val code: String? = country?.city?.code
assertNull(code)

Использование метода let ()

Мы можем использовать метод let () вместе с оператором безопасного вызова, чтобы воздействовать на переменную, не допускающую значения NULL:

val cities: List<String?> = listOf("Kolkata", null, "Mumbai")
var name: List<String?> = emptyList()
for (city in cities) {
    city?.let { name = name.plus(it) }
}
return name
assertEquals(2, name.size)

Использование метода also ()

Мы можем использовать метод also () для выполнения дополнительных операций, таких как регистрация и печать переменных, не допускающих значения NULL. Этот метод можно использовать в цепочке с методом let () или run ().

Вот как мы можем использовать метод also () вместе с методом let ():

val cities: List<String?> = listOf("Kolkata", null, "Mumbai")
var name: List<String?> = emptyList()
for (city in cities) {
    city?.let {
        name = name.plus(it)
        it
    }?.also { println("Logging the value: $it") }
}
return name
assertEquals(2, name.size)

Использование метода run ()

Мы можем использовать метод run () для выполнения некоторых операций со ссылкой, не допускающей значения NULL. Этот метод работает с использованием ссылки this и возвращает значение результата лямбда:

val countries: List<String?> = listOf("India", null, "Germany")
var name: List<String?> = emptyList()
for (country in countries) {
    country?.run {
        name = name.plus(this)
        this
    }?.also { println("Logging the value: $it") }
}
return name
assertEquals(2, name.size)

Оператор Элвиса (?:)

Мы можем использовать оператор Элвиса (? :) для возврата значения по умолчанию, только если исходная переменная имеет нулевое значение. Если левое выражение оператора Элвиса имеет значение, не допускающее обнуления, оно возвращается. В противном случае возвращается правое выражение.

Давайте посмотрим, как работает оператор Элвиса:

val country: Country? = Country(City("New Delhi", null))
val result = country?.city?.code ?: "Not available"
assertEquals("Not available", result)

Мы можем использовать оператор E lvis с оператором безопасного вызова для вызова метода или свойства переменной:

val country: Country? = Country(City("Mumbai", "002"))
val result = country?.city?.code ?: "Not available"
assertEquals("002", result)

Мы также можем использовать выражение throw и return в правом выражении операции Элвиса r. Таким образом, вместо значений по умолчанию мы можем выдавать определенные исключения в правых выражениях оператора Элвиса:

val country: Country? = Country(City("Chennai", null))
val result = country?.city?.code ?: throw IllegalArgumentException("Not a valid code")
assertThrows<IllegalArgumentException> { result }

Оператор утверждения ненулевого значения (!!)

Мы можем использовать оператор утверждения ненулевого значения (!!), чтобы явно выбросить NullPointerException. Этот оператор преобразует любую ссылку в свой тип, не допускающий значения NULL, и выдает исключение, если ссылка имеет значение NULL.

Давайте посмотрим, как мы можем выбросить NullPointerException, используя оператор утверждения not-null (!!):

val country: String? = null
val result : Int = country!!.length
assertThrows<NullPointerException> { result }

Однако, если ссылка имеет значение, не допускающее значения NULL, она выполняется успешно:

val country: String? = "India"
val result : Int = country!!.length
assertEquals(5, result)

Оператор утверждения not-null следует использовать осторожно, поскольку он является потенциальным признаком NullPointerException. Мы должны избегать использования нескольких ненулевых утверждений, подобных приведенному ниже, поскольку это затрудняет отладку того, какое свойство имеет значение null:

country!!.city!!.code

В таких случаях мы всегда должны стараться использовать безопасный оператор вызова, чтобы исключить NullPointerException:

country?.city?.code

Возможность обнуления в коллекциях

По умолчанию коллекции Kotlin не допускают значения NULL. Чтобы определить коллекцию обнуляемых типов в Kotlin, мы должны добавить вопросительный знак (?) К объявлению типа:

val countries: List<String?> = listOf("India", null, "Germany", "Russia", null)

Мы можем использовать следующий способ определения коллекции, допускающей значение NULL, в Kotlin:

var countries: List<String>? = listOf("India", "Germany", "Russia")
countries = null

Фильтрация типов, допускающих значение NULL

Мы можем отфильтровать список, содержащий значения, допускающие значение NULL, чтобы вернуть только значения, не допускающие значения NULL, с помощью метода filterNotNull ().

Давайте посмотрим на пример:

val countries: List<String?> = listOf("India", null, "Germany", "Russia", null)
val result: List<String> = countries.filterNotNull()
assertEquals(3, result.size)
assertEquals("Russia", result[2])

Заключение

В этой статье мы рассмотрим различные способы обработки ссылок, допускающих значение NULL, в Kotlin.

Код этих примеров доступен на GitHub.

Первоначально опубликовано на https://expatdev.com