Nullable var i smart cast
Rozważ następujący blok Kotlina.
var nullableInt: Int? = null
if (nullableInt != null) {
val checkedInt: Int = nullableInt
print("not-null-branch")
} else {
print("null-branch")
}
Android Studio mówi mi, że inteligentne przesyłanie z Int?do Intnie jest możliwe, ponieważ nullableIntjest zmienne. Rozumiem, że może to być problem w kodzie wielowątkowym.
Jednym ze sposobów rozwiązania tego problemu jest wykonanie jawnego rzutowania za pomocą val checkedInt: Int = nullableInt!!, ale gdybym użył zakodowanego w środowisku wielowątkowym, nie jest to zalecane.
Zamknij duplikaty
Istnieje kilka bardzo bliskich pytań dotyczących SO dotyczących tego tematu. Jednak nie znajduję satysfakcjonującej odpowiedzi w żadnej z tych, które znalazłem:
W Kotlinie, jaki jest idiomatyczny sposób radzenia sobie z wartościami zerowymi, odwoływania się do nich lub konwertowania, omawia, dlaczego pojawia się problem, ale nie zawiera sugestii, jak sobie z nim poradzić
Kotlin unika inteligentnego rzutowania dla sprawdzenia null ma gałąź if-not-null, która zwraca wartość różną od null, więc ?.let{} :? {}konstrukcja działa tam. Ponieważ moja gałąź nie-null zwraca wartość null, obie gałęzie będą działać.
Kotlin „Inteligentne rzutowanie jest niemożliwe, ponieważ właściwość mogłaby zostać zmieniona do tego czasu” dotyczy tylko gałęzi niezerowej i żadnej gałęzi zerowej, więc ?.let{}konstrukcja wydaje się poprawna. W tym wątku sugerują zrobienie lokalnej kopii przed ifoświadczeniem, co może być wykonalne również w moim przypadku. Nie jest niestety zbyt eleganckie i mam nadzieję, że istnieje inna alternatywa.
Czy istnieje sposób obsługi tego rozgałęziania warunkowego zerowego w bezpieczny sposób zerowy bez wykonywania kopii?
Rozumiem, że odpowiedź może brzmieć „to zależy”. Jeśli tak jest, powiedz to i wyjaśnij, dlaczego.
Odpowiedzi
Użyj .letzamiast?.let
Ponieważ .letfunkcja rozszerzenia jest zdefiniowana dla wszystkich typów, w tym dopuszczających wartość null, w rzeczywistości można ją wywołać bez ?.operatora bezpiecznego wywołania . Kiedy to zrobisz, lambda będzie zawsze wywoływana, nawet dla nullwartości. Parametr wewnątrz letbloku będzie dopuszczał wartość null, jeśli odbiornik ma wartość null.
Jednak parametr lambda jest uprawniony do inteligentnego odlewu , ponieważ nie jest zmienny.
Oto różnica:
x.let { it -> /* This always runs. 'it' can be null if 'x' is null */ }
x?.let { it -> /* This only runs if 'x' is not null. 'it' is never null. */ }
Stosując to do twojego przykładu, możesz napisać to:
var nullableInt: Int? = null
nullableInt.let {
if (it != null) {
doSomethingWith(it)
} else {
doSomethingElse()
}
}
Nie możesz rozpocząć rzutowania właściwości, ponieważ inny wątek może ją modyfikować. Nie ma logicznego sposobu na obejście tego. Język już zapewnia niebezpieczny sposób zrobienia tego z tym !!, o czym już wspomniałeś.
Tworzenie lokalnej kopii referencji (ręcznie lub przy użyciu funkcji zakresu, takiej jak withlub let) jest trywialne, więc nie musisz się tym martwić.
Osobiście uważam, że zmienna lokalna jest najczystszym i najbardziej czytelnym sposobem na zrobienie tego. Unikasz zagnieżdżania bloków, które miałbyś z funkcjami zakresu.
val myVar = myProp
if (myVar != null) {
} else {
}
Myślę, że najczystszym sposobem korzystania z funkcji zakresu jest użycie with. Czyta lepiej niż letpodczas obsługi obu gałęzi.
with(myProp) {
if (this != null) {
} else {
}
}
Aby uzyskać zwięzły sposób obsługi obu gałęzi, działa następująca procedura. Uważam, że alsojest trochę bardziej niezawodny niż letw tej sytuacji, ponieważ nie można przypadkowo uruchomić obu gałęzi, zwracając wartość null z pierwszej lambdy. Ale czytelność cierpi na tym zwięzłości.
myProp?.also {
// not null it
} ?: run {
// null
}