Подтвердить что ты не робот

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

Если у меня есть тип с нулевым значением Xyz?, я хочу ссылаться на него или преобразовать его в тип, не содержащий Nullable 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?. Таким образом, каков наилучший способ заставить тип значения не иметь значения NULL?

Примечание: этот вопрос намеренно написан автором и автору (Автоответчик), так что идиоматические ответы обычно спросили темы Котлина в SO. Также прояснить некоторые действительно старые ответы, написанные для альфов Котлина, которые не точны для сегодняшнего дня Котлин.

4b9b3361

Ответ 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 значения.

  • есть также оператор Безопасного (обнуляемого) приведения, который позволяет приведению к типу, не допускающему обнуления, возвращать ноль, если это невозможно. Но у меня нет действительного варианта использования для этого, который не решается другими методами, упомянутыми выше.

Ответ 2

Предыдущий ответ - это тяжелый поступок, но здесь один быстрый и простой способ:

val something: Xyz = createPossiblyNullXyz() ?: throw RuntimeError("no it shouldn't be null")
something.foo() 

Если это действительно никогда не является нулевым, исключение не произойдет, но если это когда-нибудь, вы увидите, что пошло не так.

Ответ 3

Kotlin не позволяет инициализировать нулевой объект, поэтому kotlin является Null Safety, не может уничтожить null