Ответ 1
Во-первых, вы должны прочитать все о нулевой безопасности в Kotlin, которая охватывает все случаи.
В Kotlin вы не можете получить доступ к значению NULL, если не уверены, что оно не равно null
(Проверка на NULL в условиях), или не утверждаете, что оно, безусловно, не равно null
используя !!
уверен оператор, обращаясь к нему с ?.
Безопасный вызов или, наконец, присвоение чему-либо значения, которое может быть null
по умолчанию с использованием оператора ?:
Elvis.
Для вашего первого случая в вашем вопросе у вас есть варианты, в зависимости от цели кода, который вы бы использовали один из них, и все они идиоматические, но имеют разные результаты:
val something: Xyz? = createPossiblyNullXyz()
// access it as non-null asserting that with a sure call
val result1 = something!!.foo()
// access it only if it is not null using safe operator,
// returning null otherwise
val result2 = something?.foo()
// access it only if it is not null using safe operator,
// otherwise a default value using the elvis operator
val result3 = something?.foo() ?: differentValue
// null check it with 'if' expression and then use the value,
// similar to result3 but for more complex cases harder to do in one expression
val result4 = if (something != null) {
something.foo()
} else {
...
differentValue
}
// null check it with 'if' statement doing a different action
if (something != null) {
something.foo()
} else {
someOtherAction()
}
Чтобы узнать, "Почему это работает, если выбрано значение NULL", прочитайте приведенную ниже справочную информацию о смарт-бросках.
Для вашего второго случая в вашем вопросе в вопросе с Map
, если вы как разработчик уверены, что результат никогда не будет null
, используйте !!
верный оператор как утверждение:
val map = mapOf("a" to 65,"b" to 66,"c" to 67)
val something = map.get("a")!!
something.toLong() // now valid
или в другом случае, когда карта МОЖЕТ вернуть значение NULL, но вы можете предоставить значение по умолчанию, тогда сама Map
имеет метод getOrElse
:
val map = mapOf("a" to 65,"b" to 66,"c" to 67)
val something = map.getOrElse("z") { 0 } // provide default value in lambda
something.toLong() // now valid
Исходная информация:
Примечание: в примерах ниже я использую явные типы, чтобы прояснить поведение. С выводом типа обычно типы могут быть опущены для локальных переменных и закрытых членов.
Больше о !!
верный оператор
!!
Оператор утверждает, что значение не равно null
или выбрасывает NPE. Это следует использовать в тех случаях, когда разработчик гарантирует, что значение никогда не будет null
. Думайте об этом как об утверждении, сопровождаемом умным броском.
val possibleXyz: Xyz? = ...
// assert it is not null, but if it is throw an exception:
val surelyXyz: Xyz = possibleXyz!!
// same thing but access members after the assertion is made:
possibleXyz!!.foo()
читать дальше: !! Уверенный оператор
Подробнее о null
проверке и умном приведении
Если вы защищаете доступ к обнуляемому типу с помощью проверки на null
, компилятор автоматически преобразует значение в теле оператора, чтобы оно было необнуляемым. Есть некоторые сложные потоки, где это не может произойти, но для общих случаев работает нормально.
val possibleXyz: Xyz? = ...
if (possibleXyz != null) {
// allowed to reference members:
possiblyXyz.foo()
// or also assign as non-nullable type:
val surelyXyz: Xyz = possibleXyz
}
Или, если вы делаете is
проверить не-обнуляемый тип:
if (possibleXyz is Xyz) {
// allowed to reference members:
possiblyXyz.foo()
}
И то же самое для выражений "когда", которые также безопасны:
when (possibleXyz) {
null -> doSomething()
else -> possibleXyz.foo()
}
// or
when (possibleXyz) {
is Xyz -> possibleXyz.foo()
is Alpha -> possibleXyz.dominate()
is Fish -> possibleXyz.swim()
}
Некоторые вещи не позволяют проверять null
значение умному приведению для последующего использования переменной. В приведенном выше примере используется локальная переменная, которая никоим образом не могла мутировать в потоке приложения, независимо от того, была ли в переменной val
или var
эта переменная иметь возможность преобразовать ее в null
. Но в других случаях, когда компилятор не может гарантировать анализ потока, это будет ошибкой:
var nullableInt: Int? = ...
public fun foo() {
if (nullableInt != null) {
// Error: "Smart cast to 'kotlin.Int' is impossible, because 'nullableInt' is a mutable property that could have been changed by this time"
val nonNullableInt: Int = nullableInt
}
}
Жизненный цикл переменной nullableInt
не полностью виден и может быть назначен из других потоков, то null
проверка не может быть умной литой в ненулевое значение. См. Тему "Безопасные звонки" ниже для обхода проблемы.
Другим случаем, которому интеллектуальное приведение не может доверять, чтобы оно не изменялось, является свойство val
объекта, имеющего собственный метод получения. В этом случае компилятор не видит, что именно изменяет значение, и поэтому вы получите сообщение об ошибке:
class MyThing {
val possibleXyz: Xyz?
get() { ... }
}
// now when referencing this class...
val thing = MyThing()
if (thing.possibleXyz != null) {
// error: "Kotlin: Smart cast to 'kotlin.Int' is impossible, because 'p.x' is a property that has open or custom getter"
thing.possiblyXyz.foo()
}
Подробнее: Проверка на ноль в условиях
Подробнее о ?.
Оператор безопасного звонка
Оператор безопасного вызова возвращает ноль, если значение слева равно нулю, в противном случае продолжает вычислять выражение справа.
val possibleXyz: Xyz? = makeMeSomethingButMaybeNullable()
// "answer" will be null if any step of the chain is null
val answer = possibleXyz?.foo()?.goo()?.boo()
Другой пример, когда вы хотите перебрать список, но только если он не null
и не пустой, снова пригодится оператор безопасного вызова:
val things: List? = makeMeAListOrDont()
things?.forEach {
// this loops only if not null (due to safe call) nor empty (0 items loop 0 times):
}
В одном из приведенных выше примеров у нас был случай, когда мы выполняли проверку if
но у нас был шанс, что другой поток изменил значение и, следовательно, не использовал умное приведение. Мы можем изменить этот пример, чтобы использовать оператор безопасного вызова вместе с функцией let
для решения этой проблемы:
var possibleXyz: Xyz? = 1
public fun foo() {
possibleXyz?.let { value ->
// only called if not null, and the value is captured by the lambda
val surelyXyz: Xyz = value
}
}
Подробнее: Безопасные звонки
Подробнее о ?:
Элвис Оператор
Оператор Элвиса позволяет указать альтернативное значение, если выражение слева от оператора равно null
:
val surelyXyz: Xyz = makeXyzOrNull() ?: DefaultXyz()
Он также имеет некоторые творческие применения, например, генерирует исключение, когда что-то имеет значение null
:
val currentUser = session.user ?: throw Http401Error("Unauthorized")
или вернуться рано из функции:
fun foo(key: String): Int {
val startingCode: String = codes.findKey(key) ?: return 0
// ...
return endingValue
}
подробнее: Элвис Оператор
Нулевые операторы со связанными функциями
Kotlin stdlib имеет ряд функций, которые очень хорошо работают с операторами, упомянутыми выше. Например:
// use ?.let() to change a not null value, and ?: to provide a default
val something = possibleNull?.let { it.transform() } ?: defaultSomething
// use ?.apply() to operate further on a value that is not null
possibleNull?.apply {
func1()
func2()
}
// use .takeIf or .takeUnless to turn a value null if it meets a predicate
val something = name.takeIf { it.isNotBlank() } ?: defaultName
val something = name.takeUnless { it.isBlank() } ?: defaultName
Похожие темы
В Kotlin большинство приложений пытаются избежать null
значений, но это не всегда возможно. И иногда null
имеет смысл. Некоторые рекомендации для размышления:
-
в некоторых случаях он требует разных типов возврата, которые включают статус вызова метода и результат в случае успеха. Такие библиотеки, как Result, дают тип результата успеха или сбоя, который также может веткиться в вашем коде. И библиотека Обещаний для Котлина под названием Ковенант делает то же самое в форме обещаний.
-
для коллекций в качестве возвращаемых типов всегда возвращайте пустую коллекцию вместо
null
, если только вам не нужно третье состояние "нет". Kotlin имеет вспомогательные функции, такие какemptyList()
илиemptySet()
для создания этих пустых значений. -
при использовании методов, которые возвращают обнуляемое значение, для которого у вас есть значение по умолчанию или альтернатива, используйте оператор Elvis для предоставления значения по умолчанию. В случае с
Map
используется методgetOrElse()
который позволяет генерировать значение по умолчанию, а не методMap
get()
который возвращает значение, допускающее значение NULL. То же самое дляgetOrPut()
-
при переопределении методов из Java, когда Kotlin не уверен в обнуляемости кода Java, вы всегда можете удалить
?
обнуляемость из вашего переопределения, если вы уверены, что подпись и функциональность должны быть. Поэтому ваш переопределяется метод является болееnull
сейфом. То же самое для реализации интерфейсов Java в Kotlin, измените обнуляемость на то, что, как вы знаете, является действительным. -
посмотрите на функции, которые уже могут помочь, такие как для
String?.isNullOrEmpty()
иString?.isNullOrBlank()
которые могут безопасно работать с обнуляемым значением и делать то, что вы ожидаете. Фактически, вы можете добавить свои собственные расширения, чтобы заполнить любые пробелы в стандартной библиотеке. -
Функции подтверждения, такие как
checkNotNull()
иrequireNotNull()
в стандартной библиотеке. -
вспомогательные функции, такие как
filterNotNull()
которые удаляют пустые значения из коллекций, илиlistOfNotNull()
для возврата нулевого или единственного списка элементов из возможноnull
значения. -
есть также оператор Безопасного (обнуляемого) приведения, который позволяет приведению к типу, не допускающему обнуления, возвращать ноль, если это невозможно. Но у меня нет действительного варианта использования для этого, который не решается другими методами, упомянутыми выше.