В Котлине, каков идиоматический способ борьбы с нулевыми значениями, ссылки или преобразования их
если у меня есть тип nullable Xyz?
, Я хочу сослаться на него или преобразовать его в ненулевой тип Xyz
. Каков идиоматический способ сделать это в Котлине?
например, этот код ошибки:
val something: Xyz? = createPossiblyNullXyz()
something.foo() // Error: "Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Xyz?"
но если я сначала проверю null, это разрешено, почему?
val something: Xyz? = createPossiblyNullXyz()
if (something != null) {
something.foo()
}
как изменить или обработать значение как не null
не требуется if
проверьте, предполагая, что я точно знаю, что это действительно никогда null
? Например, вот я получение значения из карты, которое я могу гарантировать, существует и результат get()
не null
. Но у меня есть ошибка:
val map = mapOf("a" to 65,"b" to 66,"c" to 67)
val something = map.get("a")
something.toLong() // Error: "Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Int?"
метод get()
считает возможным, что элемент отсутствует и возвращает тип Int?
. Поэтому, каков наилучший способ заставить тип значения не быть нулевым?
Примечание:этот вопрос намеренно написан и на него отвечает автор (Вопросы С Самостоятельным Ответом), так что идиоматические ответы на часто задаваемые темы Котлина присутствуют в SO. Также, чтобы прояснить некоторые действительно старые ответы, написанные для Альф Котлина, которые не точны для современного Котлина.
2 ответа:
во-первых, вы должны прочитать все о Нулевое Безопасность в Котлине, который тщательно охватывает случаи.
в Котлине вы не можете получить доступ к нулевому значению, не будучи уверенным, что это не
null
(проверка на null в условиях), или утверждая, что это, конечно, неnull
С помощью!!
уверен, что оператор, доступ к нему с помощью?.
Безопасный Вызов, или, наконец, давая что-то, что это возможноnull
a по умолчанию значение с помощью?:
Оператор Элвис.для вашего 1-го случая в вашем вопросе у вас есть варианты в зависимости от цели кода, который вы бы использовали один из них, и все они идиоматичны, но имеют разные результаты:
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, но вы можете указать значение по умолчанию, то есть
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
Справочная Информация:
Примечание:в примерах ниже я использую явные типы, чтобы сделать поведение четкий. При выводе типа обычно типы могут быть опущены для локальных переменных и частных членов.
подробнее о the
!!
уверен, что операторThe
!!
оператор утверждает, что значение не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
проверьте для типа не nullable: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() }
подробнее: проверка на null в условиях
подробнее о
?.
Безопасный оператор колл -оператор безопасного вызова возвращает null, если значение слева равно null, в противном случае продолжает вычислять выражение справа.
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")
или вернуться рано из a функция:
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
Связанные Разделы
в Котлине большинство приложений пытаются избежать
null
значения, но это не всегда возможно. А иногдаnull
делает совершенное чувство. Некоторые рекомендации, чтобы думать о:
в некоторых случаях он гарантирует различные типы возврата, которые включают статус вызова метода и результат в случае успеха. Библиотеки, как результат дайте вам тип результата успеха или неудачи, который также может разветвлять ваш код. А библиотека обещаний для Котлина называется Ковенант делает то же самое в виде обещаний.
для коллекций поскольку возвращаемые типы всегда возвращают пустую коллекцию вместо
null
, если вам не нужно третье состояние "нет". Котлин имеет вспомогательные функции, такие какemptyList()
илиemptySet()
для создания этих пустых значений.при использовании методов, возвращающих значение null, для которого у вас есть значение по умолчанию или альтернатива, используйте оператор Elvis для предоставления значения по умолчанию. В случае a
Map
использоватьgetOrElse()
что позволяет значение по умолчанию, которое будет создано вместоMap
методget()
который возвращает значение, допускающее значение null. То же самое дляgetOrPut()
при переопределении методов из Java, где Котлин не уверен в допустимости кода Java, вы всегда можете удалить
?
nullability от вашего переопределения, если вы уверены, что подпись и функциональность должны быть. Поэтому ваш переопределенный метод большеnull
безопасная. То же самое для реализации Java интерфейсы в Kotlin, измените nullability, чтобы быть тем, что вы знаете, действительно.посмотрите на функции, которые уже могут помочь, например, для
String?.isNullOrEmpty()
иString?.isNullOrBlank()
который может безопасно работать с нулевым значением и делать то, что вы ожидаете. Фактически, вы можете добавить свои собственные расширения, чтобы заполнить любые пробелы в стандартной библиотеке.функции утверждения типа
checkNotNull()
иrequireNotNull()
в стандартной библиотеке.вспомогательные функции, такие как
filterNotNull()
которые удалить нули из коллекций, илиlistOfNotNull()
для возврата нулевого или одного списка элементов из возможноnull
значение.есть Безопасный (nullable) оператор приведения также, что позволяет привести к ненулевому типу возвращать значение null, если это невозможно. Но у меня нет допустимого варианта использования для этого, что не решается другими методами, упомянутыми выше.
предыдущий ответ-это трудно, но вот один быстрый и простой способ:
val something: Xyz = createPossiblyNullXyz() ?: throw RuntimeError("no it shouldn't be null") something.foo()
Если это действительно никогда не будет null, исключение не произойдет, но если это когда-нибудь будет вы увидите, что пошло не так.