Inteligentne rzutowanie na „Typ” jest niemożliwe, ponieważ „zmienna” jest zmienną właściwością, którą do tego czasu można było zmienić

275

A początkujący Kotlin pyta: „dlaczego poniższy kod się nie skompiluje?”:

    var left: Node? = null

    fun show() {
         if (left != null) {
             queue.add(left) // ERROR HERE
         }
    }

Inteligentne rzutowanie na „Węzeł” jest niemożliwe, ponieważ „left” jest zmienną właściwością, którą do tego czasu można było zmienić

Rozumiem, że leftjest to zmienna zmienna, ale jawnie sprawdzam left != nulli leftjest typu, Nodewięc dlaczego nie można go rzutować na ten typ?

Jak mogę to naprawić elegancko? :)

FRR
źródło
3
Gdzieś pomiędzy innym wątkiem mógł ponownie zmienić wartość na null. Jestem pewien, że odpowiedzi na inne pytania również o tym wspominają.
nhaarman
3
Możesz użyć bezpiecznego połączenia, aby dodać
Whymarrh
dzięki @nhaarman, który ma sens, Whymarrh jak to zrobić? Myślałem, że bezpieczne wywołania dotyczą tylko obiektów, a nie metod
FRR
6
Coś w stylu: n.left?.let { queue.add(it) }myślę?
Jorn Vernee

Odpowiedzi:

357

Między wykonaniem left != nulla queue.add(left)innym wątkiem mogła zostać zmieniona wartość leftna null.

Aby obejść ten problem, masz kilka opcji. Oto niektóre:

  1. Użyj zmiennej lokalnej z inteligentnym rzutowaniem:

    val node = left
    if (node != null) {
        queue.add(node)
    }
    
  2. Użyj bezpiecznego połączenia, takiego jak jedno z poniższych:

    left?.let { node -> queue.add(node) }
    left?.let { queue.add(it) }
    left?.let(queue::add)
    
  3. Użyj operatora Elvis z, returnaby wrócić wcześniej z funkcji zamykającej:

    queue.add(left ?: return)

    Zauważ, że breaki continuemogą być używane podobnie do kontroli w pętlach.

mfulton26
źródło
8
4. Pomyśl o bardziej funkcjonalnym rozwiązaniu problemu, który nie wymaga zmiennych zmiennych.
Good Night Nerd Pride
1
@sak To była instancja Nodeklasy zdefiniowanej w oryginalnej wersji pytania, która miała bardziej skomplikowany fragment kodu n.leftzamiast po prostu left. Odpowiednio zaktualizowałem odpowiedź. Dzięki.
mfulton26
1
@sak Obowiązują te same pojęcia. Możesz utworzyć nowy valdla każdego var, zagnieździć kilka ?.letinstrukcji lub użyć kilku ?: returninstrukcji w zależności od funkcji. np MyAsyncTask().execute(a1 ?: return, a2 ?: return, a3 ?: return). Możesz także wypróbować jedno z rozwiązań dla „wynajmu wielu zmiennych” .
mfulton26,
1
@FARID do kogo się odnosi?
mfulton26
3
Tak, jest bezpieczny. Kiedy zmienna jest zadeklarowana jako klasa globalna, każdy wątek może modyfikować jej wartość. Ale w przypadku zmiennej lokalnej (zmiennej zadeklarowanej w funkcji), ta zmienna nie jest osiągalna z innych wątków, więc jest bezpieczny w użyciu.
Farid
31

1) Możesz także użyć lateinitJeśli na pewno wykonasz inicjalizację później onCreate()lub w innym miejscu.

Użyj tego

lateinit var left: Node

Zamiast tego

var left: Node? = null

2) I istnieje inny sposób, w którym używasz !!końca zmiennej, gdy używasz go w ten sposób

queue.add(left!!) // add !!
Radesh
źródło
co to robi?
c-an
@ c-an powoduje, że twoja zmienna inicjalizuje się jako null, ale oczekujesz, że zainicjuje się później w kodzie.
Radesh
Czy to nie to samo? @Radesh
c-an
@ c-to samo z czym?
Radesh
1
odpowiedziałem na powyższe pytanie, że Smart cast do „Węzła” jest niemożliwe, ponieważ „left” jest zmienną właściwością, która mogła zostać zmieniona do tego czasu, aby ten kod zapobiegał temu błędowi poprzez określenie rodzaju zmiennej. więc kompilator nie potrzebuje inteligentnej obsady
Radesh
27

Istnieje czwarta opcja oprócz tych w odpowiedzi mfulton26.

Za pomocą ?.operatora można wywoływać metody oraz pola bez zajmowania się letlub używania zmiennych lokalnych.

Trochę kodu dla kontekstu:

var factory: ServerSocketFactory = SSLServerSocketFactory.getDefault();
socket = factory.createServerSocket(port)
socket.close()//smartcast impossible
socket?.close()//Smartcast possible. And works when called

Działa z metodami, polami i wszystkimi innymi rzeczami, które próbowałem uruchomić.

Aby więc rozwiązać problem, zamiast konieczności ręcznego rzutowania lub używania zmiennych lokalnych, możesz użyć ?.metody wywoływania metod.

Dla porównania przetestowano to w Kotlinie 1.1.4-3, ale także w 1.1.51i 1.1.60. Nie ma gwarancji, że działa w innych wersjach, może to być nowa funkcja.

W tym ?.przypadku nie można użyć operatora, ponieważ jest to przekazywana zmienna, która jest problemem. Alternatywą może być operator Elvisa, który prawdopodobnie wymaga najmniejszej ilości kodu. Zamiast używać continue, returnmożna również użyć.

Opcjonalne może być także użycie ręcznego rzutowania, ale nie jest to bezpieczne:

queue.add(left as Node);

Oznacza to, że jeśli lewo zmieniło się w innym wątku, program się zawiesi.

Zoe
źródło
O ile rozumiem, „?”. operator sprawdza, czy zmienna po lewej stronie jest pusta. W powyższym przykładzie będzie to „kolejka”. Błąd „inteligentne rzutowanie niemożliwe” odnosi się do parametru „left” przekazywanego do metody „add” ... Nadal pojawia się błąd, jeśli zastosuję to podejście
FRR
Tak, błąd jest włączony lefti nie queue. Trzeba to sprawdzić, edytuje odpowiedź za minutę
Zoe
4

Praktyczny powód, dla którego to nie działa, nie jest związany z wątkami. Chodzi o to, że node.leftjest skutecznie przetłumaczone na node.getLeft().

Ten moduł pobierania właściwości można zdefiniować jako:

val left get() = if (Math.random() < 0.5) null else leftPtr

Dlatego dwa połączenia mogą nie zwracać tego samego wyniku.

Roland Illig
źródło
2

Zmień var left: Node? = nullna lateinit var left: Node. Problem rozwiązany.

Mohammed mansoor
źródło
1

Zrób to:

var left: Node? = null

fun show() {
     val left = left
     if (left != null) {
         queue.add(left) // safe cast succeeds
     }
}

To wydaje się być pierwszą opcją podaną przez zaakceptowaną odpowiedź, ale właśnie tego szukasz.

EpicPandaForce
źródło
To jest zacienianie „lewej” zmiennej?
AFD
Co jest całkowicie w porządku. Zobacz reddit.com/r/androiddev/comments/fdp2zq/…
EpicPandaForce
1

Aby możliwe było inteligentne rzutowanie właściwości, typ danych właściwości musi być klasą zawierającą metodę lub zachowanie, do którego chcesz uzyskać dostęp, a NIE, że właściwość jest typu superklasy.


np. na Androida

Być:

class MyVM : ViewModel() {
    fun onClick() {}
}

Rozwiązanie:

From: private lateinit var viewModel: ViewModel
To: private lateinit var viewModel: MyVM

Stosowanie:

viewModel = ViewModelProvider(this)[MyVM::class.java]
viewModel.onClick {}

GL

Braian Coronel
źródło
1

Najbardziej eleganckim rozwiązaniem musi być:

var left: Node? = null

fun show() {
    left?.also {
        queue.add( it )
    }
}

Wówczas nie musisz definiować nowej i niepotrzebnej zmiennej lokalnej i nie masz żadnych nowych asercji ani rzutowań (które nie są OSUSZANE). Inne funkcje zakresu również mogą działać, więc wybierz swój ulubiony.

Simon Jacobs
źródło
0

Spróbuj użyć operatora asercji niepustej ...

queue.add(left!!) 
Bikeboy
źródło
3
Niebezpieczny. Z tego samego powodu automatyczne przesyłanie nie działa.
Jacob Zimmerman
3
Może to spowodować awarię aplikacji, jeśli pozostawiona wartość jest pusta.
Pritam Karmakar
0

Jak bym to napisał:

var left: Node? = null

fun show() {
     val left = left ?: return
     queue.add(left) // no error because we return if it is null
}
tonizujące
źródło