La conversión inteligente a 'Tipo' es imposible, porque 'variable' es una propiedad mutable que podría haberse cambiado en este momento

275

Y el novato de Kotlin pregunta: "¿por qué no se compila el siguiente código?":

    var left: Node? = null

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

La conversión inteligente a 'Nodo' es imposible, porque 'izquierda' es una propiedad mutable que podría haberse cambiado en este momento

Entiendo que leftes una variable mutable, pero estoy comprobando explícitamente left != nully leftes de tipo, Node¿por qué no se puede convertir de forma inteligente en ese tipo?

¿Cómo puedo arreglar esto con elegancia? :)

FRR
fuente
3
En algún lugar entre un hilo diferente podría haber cambiado el valor a nulo nuevamente. Estoy bastante seguro de que las respuestas a las otras preguntas también lo mencionan.
nhaarman
3
Puede usar una llamada segura para agregar
Whymarrh
gracias @nhaarman que tiene sentido, ¿por qué cómo puede hacer eso? Pensé que las llamadas seguras eran solo para objetos, no métodos
FRR
66
Algo como: n.left?.let { queue.add(it) }creo?
Jorn Vernee

Respuestas:

358

Entre la ejecución de left != nully queue.add(left)otro hilo podría haber cambiado el valor de lefta null.

Para evitar esto, tiene varias opciones. Aquí están algunas:

  1. Use una variable local con conversión inteligente:

    val node = left
    if (node != null) {
        queue.add(node)
    }
  2. Use una llamada segura como una de las siguientes:

    left?.let { node -> queue.add(node) }
    left?.let { queue.add(it) }
    left?.let(queue::add)
  3. Use el operador Elvis con returnpara regresar temprano de la función de cierre:

    queue.add(left ?: return)

    Tenga en cuenta que breaky continuepuede usarse de manera similar para las comprobaciones dentro de los bucles.

mfulton26
fuente
8
4. Piense en una solución más funcional a su problema que no requiera variables mutables.
Buenas noches Orgullo Nerd
1
@sak Era una instancia de una Nodeclase definida en la versión original de la pregunta que tenía un fragmento de código más complicado en n.leftlugar de simplemente left. He actualizado la respuesta en consecuencia. Gracias.
mfulton26
1
@sak Se aplican los mismos conceptos. Puede crear una nueva valpara cada una var, anidar varias ?.letdeclaraciones o usar varias ?: returndeclaraciones según su función. por ej MyAsyncTask().execute(a1 ?: return, a2 ?: return, a3 ?: return). También puede probar una de las soluciones para una "variable múltiple let" .
mfulton26
1
@FARID a quien se refieren?
mfulton26
3
Sí, es seguro Cuando una variable se declara como clase global, cualquier subproceso puede modificar su valor. Pero en el caso de una variable local (una variable declarada dentro de una función), esa variable no es accesible desde otros hilos, por lo que es segura de usar.
Farid
31

1) También puede usar lateinitSi seguro realiza su inicialización más adelante onCreate()o en otro lugar.

Utilizar este

lateinit var left: Node

En lugar de esto

var left: Node? = null

2) Y hay otra forma de usar el !!final de variable cuando lo usas así

queue.add(left!!) // add !!
Radesh
fuente
¿Qué hace?
c-an
@ c-an hace que su variable se inicialice como nula, pero espere que se inicialice más tarde en el código.
Radesh,
Entonces, ¿no es lo mismo? @Radesh
c-an
@ c-an mismo con qué?
Radesh,
1
Respondí la pregunta anterior de que la conversión inteligente a 'Nodo' es imposible, porque 'izquierda' es una propiedad mutable que podría haberse modificado en este momento, este código evita ese error al especificar el tipo de variable. así que el compilador no necesita un reparto inteligente
Radesh,
27

Hay una cuarta opción además de las de la respuesta de mfulton26.

Al usar el ?.operador, es posible llamar a métodos y campos sin tener que lidiar leto usar variables locales.

Algún código para el contexto:

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

Funciona con métodos, campos y todas las otras cosas que intenté para que funcione.

Entonces, para resolver el problema, en lugar de tener que usar conversiones manuales o usar variables locales, puede usar ?.para llamar a los métodos.

Como referencia, esto se probó en Kotlin 1.1.4-3, pero también se probó en 1.1.51y 1.1.60. No hay garantía de que funcione en otras versiones, podría ser una nueva característica.

El uso del ?.operador no se puede usar en su caso, ya que el problema es una variable pasada. El operador de Elvis se puede usar como alternativa, y probablemente sea el que requiera la menor cantidad de código. Sin continueembargo, en lugar de usar , returntambién podría usarse.

El uso de la conversión manual también podría ser una opción, pero esto no es nulo seguro:

queue.add(left as Node);

Es decir, si la izquierda ha cambiado en un hilo diferente, el programa se bloqueará.

Zoe
fuente
Por lo que yo entiendo, el '?.' El operador está comprobando si la variable en su lado izquierdo es nula. En el ejemplo anterior sería 'cola'. El error 'elenco inteligente imposible' se refiere al parámetro "izquierda" que se pasa al método "agregar" ... Todavía obtengo el error si uso este enfoque
FRR
Correcto, el error está activado lefty no queue. Necesito verificar esto, editaré la respuesta en un minuto
Zoe
4

La razón práctica por la que esto no funciona no está relacionada con los hilos. El punto es que node.leftse traduce efectivamente en node.getLeft().

Este captador de propiedades se puede definir como:

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

Por lo tanto, dos llamadas podrían no devolver el mismo resultado.

Roland Illig
fuente
2

Cambiar var left: Node? = nulla lateinit var left: Node. Problema resuelto.

Mohammed mansoor
fuente
1

Hacer esto:

var left: Node? = null

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

Cuál parece ser la primera opción proporcionada por la respuesta aceptada, pero eso es lo que estás buscando.

EpicPandaForce
fuente
Esta es la sombra de la variable "izquierda"?
AFD
Lo cual está totalmente bien. Ver reddit.com/r/androiddev/comments/fdp2zq/…
EpicPandaForce
1

Para que haya un Smart Cast de las propiedades, el tipo de datos de la propiedad debe ser la clase que contiene el método o comportamiento al que desea acceder y NO que la propiedad sea del tipo de la superclase.


por ejemplo, en Android

Ser:

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

Solución:

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

Uso:

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

GL

Braian Coronel
fuente
1

Su solución más elegante debe ser:

var left: Node? = null

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

Entonces no tiene que definir una variable local nueva e innecesaria, y no tiene nuevas aserciones o conversiones (que no sean SECAS). Otras funciones de alcance también podrían funcionar, así que elija su favorito.

Simon Jacobs
fuente
0

Intente usar el operador de aserción no nulo ...

queue.add(left!!) 
Bikeboy
fuente
3
Peligroso. Por la misma razón, la transmisión automática no funciona.
Jacob Zimmerman el
3
Puede provocar un bloqueo de la aplicación si la izquierda es nula.
Pritam Karmakar
0

Cómo lo escribiría:

var left: Node? = null

fun show() {
     val left = left ?: return
     queue.add(left) // no error because we return if it is null
}
tonivos
fuente