Il cast intelligente su "Tipo" è impossibile, perché "variabile" è una proprietà mutabile che a questo punto avrebbe potuto essere modificata


275

E il neofita di Kotlin chiede "perché il seguente codice non viene compilato?":

    var left: Node? = null

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

Il cast intelligente su 'Nodo' è impossibile, perché 'sinistra' è una proprietà mutabile che potrebbe essere cambiata in questo momento

Ottengo che leftsia una variabile mutabile, ma sto controllando esplicitamente left != nulled leftè di tipo, Nodequindi perché non può essere smart casting per quel tipo?

Come posso risolvere questo problema in modo elegante? :)


3
Da qualche parte tra un thread diverso avrebbe potuto modificare nuovamente il valore su null. Sono abbastanza sicuro che le risposte alle altre domande menzionino anche questo.
nhaarman,

3
È possibile utilizzare una chiamata sicura per aggiungere
Whymarrh

grazie @nhaarman che ha un senso, Whymarrh come può farlo? Pensavo che le chiamate sicure fossero solo per oggetti e non per metodi
FRR

6
Qualcosa del tipo: n.left?.let { queue.add(it) }penso?
Jorn Vernee,

Risposte:


358

Tra l'esecuzione di left != nulle queue.add(left)un altro thread avrebbe potuto modificare il valore di lefta null.

Per ovviare a questo hai diverse opzioni. Qui ce ne sono alcuni:

  1. Usa una variabile locale con smart cast:

    val node = left
    if (node != null) {
        queue.add(node)
    }
  2. Utilizzare una chiamata sicura come una delle seguenti:

    left?.let { node -> queue.add(node) }
    left?.let { queue.add(it) }
    left?.let(queue::add)
  3. Utilizzare l' operatore Elvis con returnper tornare presto dalla funzione di chiusura:

    queue.add(left ?: return)

    Si noti che breake continuepuò essere utilizzato in modo simile per i controlli all'interno dei loop.


8
4. Pensa a una soluzione più funzionale al tuo problema che non richiede variabili mutabili.
Good Night Nerd Pride,

1
@sak Era un'istanza di una Nodeclasse definita nella versione originale della domanda con uno snippet di codice più complicato n.leftinvece che semplicemente left. Ho aggiornato la risposta di conseguenza. Grazie.
mfulton26,

1
@sak Si applicano gli stessi concetti. È possibile creare un nuovo valper ciascuno var, nidificare più ?.letistruzioni o utilizzare più ?: returnistruzioni a seconda della funzione. es MyAsyncTask().execute(a1 ?: return, a2 ?: return, a3 ?: return). Puoi anche provare una delle soluzioni per una "variabile multipla let" .
mfulton26,

1
@FARID a cui si riferiscono?
mfulton26,

3
Sì, è sicuro. Quando una variabile viene dichiarata come classe globale, qualsiasi thread può modificarne il valore. Ma nel caso di una variabile locale (una variabile dichiarata all'interno di una funzione), quella variabile non è raggiungibile da altri thread, quindi è sicura da usare.
Farid

31

1) Inoltre puoi usare lateinitSe sei sicuro di effettuare l'inizializzazione in un secondo momento onCreate()o altrove.

Usa questo

lateinit var left: Node

Invece di questo

var left: Node? = null

2) E c'è un altro modo che usa la !!fine della variabile quando la usi in questo modo

queue.add(left!!) // add !!

Che cosa fa?
c-an

@ c-an rende la variabile inizializzata come nulla ma si aspetta che venga inizializzata successivamente nel codice.
Radesh,

Quindi, non è lo stesso? @Radesh
c-an

@ c-an same with what?
Radesh

1
ho risposto sopra la domanda che è Smart cast su 'Nodo' è impossibile, perché 'sinistra' è una proprietà mutabile che potrebbe essere stata modificata in questo momento questo codice impedisce quell'errore specificando il tipo di variabile. così il compilatore non ha bisogno di cast intelligente
Radesh

27

C'è una quarta opzione oltre a quelle nella risposta di mfulton26.

Utilizzando l' ?.operatore è possibile chiamare metodi e campi senza occuparsi leto utilizzare variabili locali.

Alcuni codici per il contesto:

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

Funziona con metodi, campi e tutte le altre cose che ho provato a farlo funzionare.

Quindi, al fine di risolvere il problema, invece di utilizzare i cast manuali o le variabili locali, è possibile utilizzare ?.per chiamare i metodi.

Per riferimento, questo è stato testato in Kotlin 1.1.4-3, ma anche testato in 1.1.51e 1.1.60. Non c'è garanzia che funzioni su altre versioni, potrebbe essere una nuova funzionalità.

L'uso ?.dell'operatore non può essere utilizzato nel tuo caso poiché è una variabile passata il problema. L'operatore Elvis può essere utilizzato come alternativa ed è probabilmente quello che richiede la minima quantità di codice. Invece di usare continueperò, returnpotrebbe anche essere usato.

L'uso del casting manuale potrebbe anche essere un'opzione, ma questo non è nullo sicuro:

queue.add(left as Node);

Significa che se il tasto sinistro è cambiato su un thread diverso, il programma si arresterà in modo anomalo.


Per quanto ho capito, il '?.' l'operatore sta verificando se la variabile sul lato sinistro è nulla. Nell'esempio sopra sarebbe "coda". L'errore "smart cast impossibile" si riferisce al parametro "lasciato" che viene passato al metodo "aggiungi" ... Ottengo ancora l'errore se uso questo approccio
FRR

Bene, l'errore è attivo lefte non queue. È necessario controllare questo, modificherà la risposta in un minuto
Zoe

4

Il motivo pratico per cui questo non funziona non è legato ai thread. Il punto è che node.leftè effettivamente tradotto in node.getLeft().

Questo getter di proprietà potrebbe essere definito come:

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

Pertanto, due chiamate potrebbero non restituire lo stesso risultato.




1

Perché ci sia uno Smart Cast delle proprietà, il tipo di dati della proprietà deve essere la classe che contiene il metodo o il comportamento a cui si desidera accedere e NON che la proprietà sia del tipo della superclasse.


ad es. su Android

Essere:

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

Soluzione:

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

Uso:

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

GL


1

La tua soluzione più elegante deve essere:

var left: Node? = null

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

Quindi non devi definire una variabile locale nuova e non necessaria e non hai nuove asserzioni o cast (che non sono DRY). Anche altre funzioni dell'oscilloscopio potrebbero funzionare, quindi scegli il tuo preferito.


0

Prova a utilizzare l'operatore di asserzione non null ...

queue.add(left!!) 

3
Pericoloso. Per lo stesso motivo il casting automatico non funziona.
Jacob Zimmerman,

3
Potrebbe causare un arresto anomalo dell'app se lasciato è nullo.
Pritam Karmakar,

0

Come lo scriverei:

var left: Node? = null

fun show() {
     val left = left ?: return
     queue.add(left) // no error because we return if it is null
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.