W Kotlinie, jaki jest idiomatyczny sposób radzenia sobie z wartościami zerowymi, odwoływania się do nich lub ich konwertowania

Jeśli mam typ nullable Xyz?, chcę go odwołać lub przekonwertować na typ nie-nullable Xyz. Jaki jest idiomatyczny sposób robienia tego w Kotlinie?

Na przykład ten kod jest w błędzie:

val something: Xyz? = createPossiblyNullXyz()
something.foo() // Error: "Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Xyz?"

Ale jeśli najpierw sprawdzę null to jest dozwolone, dlaczego?

val something: Xyz? = createPossiblyNullXyz()
if (something != null) {
    something.foo() 
}

Jak zmienić lub potraktować wartość jako nie null bez konieczności sprawdzania if, zakładając, że wiem na pewno, że naprawdę nigdy null? Na przykład tutaj pobieram wartość z mapy, którą mogę gwarancja istnieje, a wynik get() nie jest null. Ale mam błąd:

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?"

Metoda get() uważa, że możliwe jest brak elementu i zwraca typ Int?. W związku z tym, jaki jest najlepszy sposób, aby wymusić, że typ wartości nie jest nullable?

Uwaga: to pytanie jest celowo napisane i odpowiedziane przez autora (Self-answer Questions), tak aby idiomatyczne odpowiedzi na najczęściej zadawane tematy Kotlina były obecne w SO. Również aby wyjaśnić kilka naprawdę starych odpowiedzi napisanych dla Alf z Kotlina, które nie są dokładne dla obecnego Kotlina.

Author: Jayson Minard, 2015-12-28

2 answers

Po pierwsze, powinieneś przeczytać wszystko o zerowym bezpieczeństwie w Kotlinie, który dokładnie Obejmuje Przypadki.

W Kotlinie nie można uzyskać dostępu do wartości nullable bez pewności, że nie jest null (sprawdzanie null w Warunkach ), lub stwierdzenie, że z pewnością nie jest to null za pomocą !! Operator sure, dostęp do niego za pomocą ?. bezpieczne wywołanie , lub wreszcie podanie czegoś, co prawdopodobnie jest null wartością domyślną przy użyciu ?: Elvis Operator .

Dla pierwszego przypadku w twoim pytaniu masz opcje w zależności od intencji kodu, którego użyjesz, a wszystkie są idiomatyczne, ale mają różne wyniki:

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() 
}

Dla "Dlaczego to działa, gdy null zaznaczone" przeczytaj informacje podstawowe poniżej na smart casts .

W drugim przypadku w pytaniu w pytaniu z Map, Jeśli jako deweloper jesteś pewien, że wynik nigdy nie będzie null, użyj !! pewny operator jako twierdzenie:

val map = mapOf("a" to 65,"b" to 66,"c" to 67)
val something = map.get("a")!!
something.toLong() // now valid

Lub w innym przypadku, gdy mapa może zwrócić null, ale można podać wartość domyślną, wtedy Map sama ma getOrElse metoda :

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

Informacje Ogólne:

Uwaga: w poniższych przykładach używam jawnych typów, aby zachowanie było jasne. Przy wnioskowaniu typu, Zwykle typy mogą być pomijane dla zmiennych lokalnych i członków prywatnych.

Więcej o operator !! sure

Operator !! twierdzi, że wartość nie jest null lub rzuca NPE. Powinno to być stosowane w przypadkach, gdy deweloper gwarantuje, że wartość nigdy nie będzie null. Pomyśl o tym jak o twierdzeniu, po którym następuje inteligentna Obsada .

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()

Czytaj więcej: !! Operator Sure


Więcej o null sprawdzanie i inteligentne Odlewy

Jeśli zabezpieczysz dostęp do typu nullable za pomocą sprawdzania null , kompilator Smart cast wartość w ciele instrukcji będzie nie nullable. Istnieją pewne skomplikowane przepływy, w których nie może się to zdarzyć, ale w zwykłych przypadkach działa dobrze.

val possibleXyz: Xyz? = ...
if (possibleXyz != null) {
   // allowed to reference members:
   possiblyXyz.foo()
   // or also assign as non-nullable type:
   val surelyXyz: Xyz = possibleXyz
}

Lub jeśli wykonasz is sprawdzenie, czy typ nie jest nullable:

if (possibleXyz is Xyz) {
   // allowed to reference members:
   possiblyXyz.foo()
}

I to samo dla wyrażeń "kiedy", które również bezpiecznie rzucają:

when (possibleXyz) {
    null -> doSomething()
    else -> possibleXyz.foo()
}

// or

when (possibleXyz) {
    is Xyz -> possibleXyz.foo()
    is Alpha -> possibleXyz.dominate()
    is Fish -> possibleXyz.swim() 
}

Niektóre rzeczy nie pozwalają null sprawdzić smart cast do późniejszego użycia zmiennej. Powyższy przykład wykorzystuje zmienna lokalna, która w żaden sposób nie mogła zmutować w przepływie aplikacji, niezależnie od tego, czy val czy var ta zmienna nie miała możliwości mutacji w null. Jednak w innych przypadkach, gdy kompilator nie może zagwarantować analizy przepływu, byłby to błąd:

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
    }
}

Cykl życia zmiennej nullableInt nie jest całkowicie widoczny i może być przypisany z innych wątków, sprawdzenie null nie może być Smart cast do wartości nie nullable. Zobacz temat "bezpieczne połączenia" poniżej na obejście.

Innym przypadkiem, który nie może być zaufany przez smart cast , aby nie mutować, jest właściwość val na obiekcie, który ma własny getter. W tym przypadku kompilator nie ma wglądu w to, co mutuje wartość i dlatego otrzymasz komunikat o błędzie:

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()
}

Czytaj więcej: sprawdzanie null w Warunkach


Więcej o ?. Operator bezpiecznego połączenia

Operator bezpiecznego połączenia zwraca null, jeśli wartość po lewej jest null, w przeciwnym razie kontynuuje Obliczanie wyrażenia po prawej.

val possibleXyz: Xyz? = makeMeSomethingButMaybeNullable()
// "answer" will be null if any step of the chain is null
val answer = possibleXyz?.foo()?.goo()?.boo()

Inny przykład, w którym chcesz iterować listę, ale tylko jeśli nie null i nie pusta, ponownie przydaje się Operator bezpiecznego połączenia:

val things: List? = makeMeAListOrDont()
things?.forEach {
    // this loops only if not null (due to safe call) nor empty (0 items loop 0 times):
}

W jednym z powyższych przykładów mieliśmy przypadek, w którym zrobiliśmy if sprawdzenie, ale mamy szansę, że inny wątek zmutował wartość i dlatego nie ma smart cast . Możemy zmienić tę próbkę, aby użyć operatora bezpiecznego połączenia wraz z funkcja let do rozwiązania tego:

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
    }
}

Czytaj więcej: bezpieczne połączenia


Więcej o operatorze ?: Elvis

Operator Elvisa pozwala na podanie wartości alternatywnej, gdy wyrażenie po lewej stronie operatora to null:

val surelyXyz: Xyz = makeXyzOrNull() ?: DefaultXyz()
Ma również kilka kreatywnych zastosowań, na przykład rzuca wyjątek, gdy coś jest null: {70]}
val currentUser = session.user ?: throw Http401Error("Unauthorized")

Lub zwracać wcześniej z funkcji:

fun foo(key: String): Int {
   val startingCode: String = codes.findKey(key) ?: return 0
   // ...
   return endingValue
}

Czytaj więcej: Operator Elvisa


Operatory Null Z FUNKCJAMI pokrewnymi

Kotlin stdlib posiada szereg funkcji, które bardzo dobrze współpracują z operatorami wymienionymi powyżej. Na przykład:

// 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

Tematy Pokrewne

W Kotlinie większość aplikacji stara się unikać wartości null, ale nie zawsze jest to możliwe. I czasami null ma sens. Niektóre wskazówki do przemyślenia:

  • W niektórych przypadkach gwarantuje różne typy zwrotów, które obejmują status wywołania metody i wynik, jeśli się powiedzie. Biblioteki takie jak Result dają Typ wyniku sukcesu lub porażki, który może również rozgałęziać Twój kod. I biblioteka obietnic dla Kotlina o nazwie Kovenant robi to samo w formie obietnic.

  • W przypadku kolekcji jako typów zwracanych zawsze zwraca pustą kolekcję zamiast null, chyba że potrzebujesz trzeciego stanu "not present". Kotlin ma funkcje pomocnicze, takie jak emptyList() lub emptySet() aby utworzyć te puste wartości.

  • Używając metod, które zwracają wartość nullable, dla której masz wartość domyślną lub alternatywną, użyj operatora Elvis, aby podać wartość domyślną. W przypadku Map użyj getOrElse() pozwala na wygenerowanie wartości domyślnej zamiast metody Map get(), która zwraca wartość nullable. To samo dla getOrPut()

  • Przy nadpisywaniu metod w Javie, gdzie Kotlin nie jest pewien, czy kod Java jest zerowalny, zawsze możesz usunąć ? zerowalność z nadpisywania, jeśli jesteś pewien, jaki powinien być podpis i funkcjonalność. Dlatego Twoja metoda overridden jest bardziej null Bezpieczna. To samo dotyczy implementacji interfejsów Java w Kotlinie, Zmień nullability na to, co wiesz, że jest poprawne.

  • Spójrz na funkcje, które mogą już pomóc, takie jak dla String?.isNullOrEmpty() oraz String?.isNullOrBlank() które mogą działać na wartości nullable bezpiecznie i rób to, czego oczekujesz. W rzeczywistości, można dodać własne rozszerzenia, aby wypełnić wszelkie luki w bibliotece standardowej.

  • Funkcje Asercyjne jak checkNotNull() oraz requireNotNull() w bibliotece standardowej.

  • Funkcje pomocnicze jak filterNotNull() które usuwają nulle ze zbiorów, lub listOfNotNull() do zwracania listy zerowej lub pojedynczej pozycji z ewentualnie null wartości.

  • Jest Bezpieczny (nullable) operator cast jak również, który pozwala cast do typu non nullable zwracać null, jeśli nie jest to możliwe. Ale nie mam poprawnego przypadku użycia tego, który nie jest rozwiązany przez inne metody wymienione powyżej.

 223
Author: Jayson Minard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-25 15:36:16

Poprzednia odpowiedź jest trudna do naśladowania, ale jest jeden szybki i łatwy sposób:

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

Jeśli to naprawdę nigdy nie jest null, wyjątek się nie stanie, ale jeśli kiedykolwiek będzie, zobaczysz, co poszło nie tak.

 1
Author: John Farrell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-10 05:22:13