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


если у меня есть тип 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 125

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, исключение не произойдет, но если это когда-нибудь будет вы увидите, что пошло не так.