Во-первых, вы должны прочитать все о нулевой безопасности в Kotlin, которая охватывает все случаи.
В Kotlin вы не можете получить доступ к значению NULL, не будучи уверенным, что это не так null
( Проверка на NULL в условиях ), или утверждая, что он, безусловно, не null
использует !!
оператор sure , обращаясь к нему с помощью ?.
безопасного вызова или, наконец, давая что-то, что, возможно null
, значение по умолчанию с использованием ?:
оператора Элвиса .
Для вашего первого случая в вашем вопросе у вас есть варианты, в зависимости от цели кода, который вы бы использовали один из них, и все они идиоматические, но имеют разные результаты:
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», прочтите приведенную ниже справочную информацию о смарт-приведениях .
Для вашего 2-го случая в вашем вопросе в вопросе с 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()
}
Подробнее: Проверка на ноль в условиях
Подробнее об ?.
операторе Safe Call
Оператор безопасного вызова возвращает ноль, если значение слева равно нулю, в противном случае продолжает вычислять выражение справа.
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, дают тип результата успеха или сбоя, который также может ветвиться в вашем коде. И библиотека Promises для Kotlin под названием Kovenant делает то же самое в форме обещаний.
для коллекций в качестве возвращаемых типов всегда возвращайте пустую коллекцию вместо a null
, если только вам не нужно третье состояние «отсутствует». Kotlin имеет вспомогательные функции, такие как emptyList()
илиemptySet()
для создания этих пустых значений.
при использовании методов, которые возвращают обнуляемое значение, для которого у вас есть значение по умолчанию или альтернатива, используйте оператор Элвиса для предоставления значения по умолчанию. В случае Map
использования, getOrElse()
которое позволяет генерировать значение по умолчанию вместо Map
метода, get()
который возвращает значение, допускающее обнуление. То же самое дляgetOrPut()
при переопределении методов из Java, когда Kotlin не уверен в обнуляемости кода Java, вы всегда можете ?
исключить обнуляемость из переопределения, если вы уверены, какой должна быть подпись и функциональность. Поэтому ваш переопределенный метод более null
безопасен. То же самое для реализации интерфейсов Java в Kotlin, измените обнуляемость на то, что, как вы знаете, является действительным.
посмотрите на функции, которые уже могут помочь, например, для которых String?.isNullOrEmpty()
и String?.isNullOrBlank()
которые могут безопасно работать со значением NULL, и делайте то, что вы ожидаете. Фактически, вы можете добавить свои собственные расширения, чтобы заполнить любые пробелы в стандартной библиотеке.
Утверждение функций, как checkNotNull()
и requireNotNull()
в стандартной библиотеке.
вспомогательные функции, например, filterNotNull()
которые удаляют пустые значения из коллекций, или listOfNotNull()
для возврата нулевого или единственного списка элементов из возможного null
значения.
также есть оператор Безопасного (обнуляемого) приведения, который позволяет приведению к типу, не допускающему обнуления, возвращать ноль, если это невозможно. Но у меня нет действительного варианта использования для этого, который не решается другими методами, упомянутыми выше.