Najpierw powinieneś przeczytać wszystko o Null Safety w Kotlinie, które dokładnie omawia sprawy.
W Kotlin, nie można uzyskać dostępu do wartości zerowalne nie będąc pewna, że to nie jest null
( Sprawdzanie wartości null w warunkach ) lub twierdząc, że jest on na pewno nie null
za pomocą !!
pewny operatora , dostępu do niego z ?.
awaryjnym Wezwania , albo wreszcie dając coś, co jest prawdopodobnie null
wartość domyślna przy użyciu ?:
operatora Elvisa .
W pierwszym przypadku w Twoim pytaniu masz opcje w zależności od przeznaczenia kodu, którego użyłbyś jednej z nich, i 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()
}
W sekcji „Dlaczego to działa, gdy zaznaczono wartość null”, przeczytaj poniższe informacje na temat inteligentnych rzutów .
W drugim przypadku w pytaniu z pytaniem Map
, jeśli jako programista jesteś pewien, że wynik nigdy nie będzie null
, użyj !!
pewnego operatora jako potwierdzenia:
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ć wartość null, ale możesz podać wartość domyślną, wtedy Map
sama ma getOrElse
metodę :
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 wyjaśnić zachowanie. W przypadku wnioskowania o typie typy można zwykle pominąć w przypadku zmiennych lokalnych i prywatnych elementów członkowskich.
Więcej o !!
pewnym operatorze
!!
Operator stwierdza, że wartość ta nie jest null
lub generuje NPE. Powinno to być używane w przypadkach, gdy programista gwarantuje, że wartość nigdy nie będzie null
. Pomyśl o tym jako o asercie, po którym następuje sprytna 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: !! Pewnie operator
Więcej informacji o null
sprawdzaniu i inteligentnych rzutach
Jeśli chronisz dostęp do typu dopuszczającego wartość null za pomocą null
czeku, kompilator inteligentnie rzutuje wartość w treści instrukcji, aby nie dopuszczała wartości null. Istnieje kilka skomplikowanych przepływów, w których nie może się to zdarzyć, ale w typowych 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 is
sprawdzisz typ nie dopuszczający wartości null:
if (possibleXyz is Xyz) {
// allowed to reference members:
possiblyXyz.foo()
}
To samo dotyczy wyrażeń „kiedy”, które również są bezpieczne:
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ą na null
sprawdzenie inteligentnego rzutowania w celu późniejszego użycia zmiennej. Powyższy przykład wykorzystuje zmienną lokalną, która w żaden sposób nie mogło być zmutowany w przepływie aplikacji, czy val
czy var
ta zmienna nie miał okazji do mutacji w null
. Ale 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 w pełni widoczny i może być przypisany z innych wątków, null
sprawdzenie nie może być inteligentnie rzutowane na wartość nieprzekraczającą wartości null. Zobacz temat „Bezpieczne połączenia” poniżej, aby poznać obejście tego problemu.
Innym przypadkiem, któremu inteligentne rzutowanie nie może zaufać, aby nie ulegać mutacji, jest val
właściwość obiektu, który ma niestandardowy moduł pobierający. W takim przypadku kompilator nie ma wglądu w to, co zmienia wartość, i dlatego zostanie wyświetlony 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 ?.
operatorze Safe Call
Operator bezpiecznego wywołania zwraca wartość null, jeśli wartość po lewej stronie jest równa null, w przeciwnym razie kontynuuje obliczanie wyrażenia po prawej stronie.
val possibleXyz: Xyz? = makeMeSomethingButMaybeNullable()
// "answer" will be null if any step of the chain is null
val answer = possibleXyz?.foo()?.goo()?.boo()
Kolejny przykład, w którym chcesz iterować listę, ale tylko wtedy, gdy nie jest null
i nie jest pusta, ponownie przydaje się bezpieczny operator połączeń:
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 if
sprawdziliśmy, ale mieliśmy szansę, że inny wątek zmutował wartość, a zatem nie sprytnie rzucał . Możemy zmienić ten przykład, aby użyć bezpiecznego operatora połączenia wraz z let
funkcją rozwiązującą ten problem:
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 ?:
Elvis Operator
Operator Elvis umożliwia podanie alternatywnej wartości, gdy wyrażenie po lewej stronie operatora to null
:
val surelyXyz: Xyz = makeXyzOrNull() ?: DefaultXyz()
Ma też kilka kreatywnych zastosowań, na przykład rzuca wyjątek, gdy coś jest null
:
val currentUser = session.user ?: throw Http401Error("Unauthorized")
lub aby wrócić wcześniej z funkcji:
fun foo(key: String): Int {
val startingCode: String = codes.findKey(key) ?: return 0
// ...
return endingValue
}
czytaj więcej: Elvis Operator
Operatory zerowe z powiązanymi funkcjami
Kotlin stdlib ma szereg funkcji, które bardzo dobrze współpracują z wyżej wymienionymi operatorami. 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
Powiązane tematy
W Kotlinie większość aplikacji stara się unikać null
wartości, ale nie zawsze jest to możliwe. Czasami null
ma to sens. Kilka wskazówek do przemyślenia:
w niektórych przypadkach gwarantuje różne typy zwracane, które obejmują stan wywołania metody i wynik, jeśli się powiedzie. Biblioteki, takie jak Result, podają typ wyniku sukcesu lub niepowodzenia, który może również rozgałęzić kod. A biblioteka Promises dla Kotlina o nazwie Kovenant robi to samo w formie obietnic.
w przypadku kolekcji jako typy zwracane zawsze zwracają pustą kolekcję zamiast a null
, chyba że potrzebny jest trzeci stan „nieobecny”. Kotlin ma funkcje pomocnicze, takie jak emptyList()
lubemptySet()
do tworzenia tych pustych wartości.
gdy używasz metod, które zwracają wartość dopuszczającą wartość null, dla której masz wartość domyślną lub alternatywną, użyj operatora Elvis, aby podać wartość domyślną. W przypadku Map
użycia, getOrElse()
która pozwala na wygenerowanie wartości domyślnej zamiast Map
metody, get()
która zwraca wartość dopuszczającą wartość null. To samo dotyczygetOrPut()
podczas zastępowania metod z języka Java, w przypadku których Kotlin nie jest pewien, czy kod Java nie może mieć ?
wartości null, zawsze możesz usunąć wartość null z zastąpienia, jeśli masz pewność, jaka powinna być sygnatura i funkcjonalność. Dlatego Twoja nadpisana metoda jest null
bezpieczniejsza. To samo dotyczy implementowania interfejsów Java w Kotlin, zmień wartość null na to, co wiesz, że jest prawidłowe.
spójrz na funkcje, które mogą już pomóc, takie jak for String?.isNullOrEmpty()
i String?.isNullOrBlank()
które mogą bezpiecznie operować na wartości zerowej i zrób to, czego oczekujesz. W rzeczywistości możesz dodać własne rozszerzenia, aby wypełnić wszelkie luki w standardowej bibliotece.
funkcje asercji, takie jak checkNotNull()
iw requireNotNull()
bibliotece standardowej.
funkcje pomocnicze, takie jak filterNotNull()
usuwanie wartości null z kolekcji lub listOfNotNull()
zwracanie listy zerowej lub pojedynczej pozycji z możliwej null
wartości.
istnieje również operator rzutowania Safe (dopuszczający wartość null), który umożliwia rzutowanie na typ, który nie dopuszcza wartości null, zwraca wartość null, jeśli nie jest to możliwe. Ale nie mam dla tego ważnego przypadku użycia, który nie zostałby rozwiązany innymi metodami wymienionymi powyżej.