A conversão inteligente para 'Tipo' é impossível, porque 'variável' é uma propriedade mutável que poderia ter sido alterada nesse momento

275

E o novato em Kotlin pergunta: "Por que o código a seguir não é compilado?":

    var left: Node? = null

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

A conversão inteligente para 'Nó' é impossível, porque 'esquerda' é uma propriedade mutável que poderia ter sido alterada nesse momento

Recebo que lefté uma variável mutável, mas estou verificando explicitamente left != nulle lefté do tipo, Nodepor que não pode ser transmitida de forma inteligente para esse tipo?

Como posso corrigir isso de forma elegante? :)

FRR
fonte
3
Em algum lugar entre um encadeamento diferente poderia ter alterado o valor para nulo novamente. Tenho certeza de que as respostas para as outras perguntas mencionam isso também.
Nhaarman
3
Você pode usar uma chamada segura para adicionar
Whymarrh
obrigado @nhaarman que faz sentido, por que Marrh como pode fazer isso? Pensei chamadas seguras eram apenas para objetos não métodos
FRR
6
Algo como: n.left?.let { queue.add(it) }eu acho?
Jorn Vernee

Respostas:

358

Entre execução de left != nulle queue.add(left)outro encadeamento poderia ter alterado o valor de leftpara null.

Para contornar isso, você tem várias opções. Aqui estão alguns:

  1. Use uma variável local com conversão inteligente:

    val node = left
    if (node != null) {
        queue.add(node)
    }
  2. Use uma chamada segura , como uma das seguintes opções:

    left?.let { node -> queue.add(node) }
    left?.let { queue.add(it) }
    left?.let(queue::add)
  3. Use o operador Elvis com returnpara retornar cedo da função de fechamento:

    queue.add(left ?: return)

    Observe que breake continuepode ser usado de forma semelhante para verificações dentro de loops.

mfulton26
fonte
8
4. Pense em uma solução mais funcional para o seu problema que não exija variáveis ​​mutáveis.
Good Nerd Pride
1
@sak Era uma instância de uma Nodeclasse definida na versão original da pergunta que tinha um trecho de código mais complicado, em n.leftvez de simplesmente left. Atualizei a resposta de acordo. Obrigado.
mfulton26
1
@sak Os mesmos conceitos se aplicam. Você pode criar um novo valpara cada um var, aninhar várias ?.letinstruções ou usar várias ?: returninstruções, dependendo da sua função. por exemplo MyAsyncTask().execute(a1 ?: return, a2 ?: return, a3 ?: return). Você também pode tentar uma das soluções para uma "variável múltipla" .
precisa saber é o seguinte
1
@FARID a quem se refere?
mfulton26
3
Sim, é seguro. Quando uma variável é declarada como classe global, qualquer encadeamento pode modificar seu valor. Porém, no caso de uma variável local (uma variável declarada dentro de uma função), essa variável não pode ser acessada por outros threads, portanto, é seguro usá-la.
Farid
31

1) Também é possível usar lateinitSe você com certeza inicializar mais tarde onCreate()ou em outro lugar.

Usa isto

lateinit var left: Node

Em vez disso

var left: Node? = null

2) E existe outra maneira de usar o !!fim da variável quando você a usa assim

queue.add(left!!) // add !!
Radesh
fonte
O que isso faz?
c-an
@ c-an faz com que sua variável seja inicializada como nula, mas espere que ela seja inicializada mais tarde no código.
Radesh 7/05/19
Então, não é o mesmo? @Radesh
c-an
@ c-mesmo com o que?
Radesh 7/05/19
1
Eu respondi acima a pergunta que é Smart cast para 'Node' é impossível, porque 'left' é uma propriedade mutável que poderia ter sido alterada a essa altura. Este código evita esse erro especificando o tipo de variável. assim compilador não há necessidade de elenco inteligente
Radesh
27

Existe uma quarta opção além das da resposta de mfulton26.

Ao usar o ?.operador, é possível chamar métodos e campos sem lidar com letou usar variáveis ​​locais.

Algum código para o contexto:

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

Funciona com métodos, campos e todas as outras coisas que tentei fazer funcionar.

Portanto, para resolver o problema, em vez de precisar usar projeções manuais ou variáveis ​​locais, você pode usar ?.para chamar os métodos.

Para referência, isso foi testado em Kotlin 1.1.4-3, mas também testado em 1.1.51e 1.1.60. Não há garantia de que funcione em outras versões; pode ser um novo recurso.

O uso do ?.operador não pode ser usado no seu caso, já que é uma variável transmitida que é o problema. O operador Elvis pode ser usado como uma alternativa e provavelmente é o que requer a menor quantidade de código. Em vez de usar continue, porém, returntambém pode ser usado.

O uso da transmissão manual também pode ser uma opção, mas isso não é seguro para nulos:

queue.add(left as Node);

Ou seja, se a esquerda foi alterada em um segmento diferente, o programa falhará.

Zoe
fonte
Tanto quanto eu entendo, o '?'. O operador está verificando se a variável no lado esquerdo é nula. No exemplo acima, seria 'fila'. O erro 'elenco inteligente impossível' está se referindo ao parâmetro "esquerda" que está sendo passado para o método "add" ... Eu ainda obter o erro se eu usar essa abordagem
FRR
Certo, o erro está ativado lefte não queue. Necessidade de verificar isso, vai editar a resposta em um minuto
Zoe
4

A razão prática pela qual isso não funciona não está relacionada a threads. O ponto é que node.lefté efetivamente traduzido para node.getLeft().

Este getter de propriedade pode ser definido como:

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

Portanto, duas chamadas podem não retornar o mesmo resultado.

Roland Illig
fonte
2

Mude var left: Node? = nullpara lateinit var left: Node. Problema resolvido.

Mohammed mansoor
fonte
1

Faça isso:

var left: Node? = null

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

Qual parece ser a primeira opção fornecida pela resposta aceita, mas é isso que você está procurando.

EpicPandaForce
fonte
Isso é sombreamento da variável "esquerda"?
AFD
O que é totalmente ok. Veja reddit.com/r/androiddev/comments/fdp2zq/…
EpicPandaForce
1

Para que haja uma conversão inteligente das propriedades, o tipo de dados da propriedade deve ser a classe que contém o método ou o comportamento que você deseja acessar e NÃO que a propriedade seja do tipo da superclasse.


por exemplo, no Android

Estar:

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

Solução:

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

Uso:

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

GL

Braian Coronel
fonte
1

Sua solução mais elegante deve ser:

var left: Node? = null

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

Então você não precisa definir uma variável local nova e desnecessária e não possui nenhuma nova asserção ou conversão (que não seja SECA). Outras funções de escopo também podem funcionar, então escolha o seu favorito.

Simon Jacobs
fonte
0

Tente usar o operador de asserção não nulo ...

queue.add(left!!) 
Bikeboy
fonte
3
Perigoso. Pelo mesmo motivo, a transmissão automática não funciona.
Jacob Zimmerman
3
Pode causar falha no aplicativo, se for deixado nulo.
Pritam Karmakar
0

Como eu escreveria:

var left: Node? = null

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