La conversion intelligente en «Type» est impossible, car «variable» est une propriété modifiable qui aurait pu être modifiée à ce moment


275

Et le débutant de Kotlin demande: "Pourquoi le code suivant ne compilera-t-il pas?":

    var left: Node? = null

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

La conversion intelligente vers 'Node' est impossible, car 'left' est une propriété modifiable qui aurait pu être modifiée à ce moment

J'obtiens que leftc'est une variable mutable, mais je vérifie explicitement left != nullet leftest de type Nodealors pourquoi ne peut-il pas être casté intelligemment dans ce type?

Comment puis-je résoudre ce problème avec élégance? :)


3
Quelque part entre un thread différent aurait pu changer la valeur en null à nouveau. Je suis presque sûr que les réponses aux autres questions le mentionnent également.
nhaarman

3
Vous pouvez utiliser un appel sûr pour ajouter
Whymarrh

merci @nhaarman qui a du sens, Whymarrh comment faire ça? Je pensais que les appels sécurisés ne concernaient que des objets et non des méthodes
FRR

6
Quelque chose comme: n.left?.let { queue.add(it) }je pense?
Jorn Vernee

Réponses:


359

Entre l'exécution de left != nullet queue.add(left)un autre thread aurait pu changer la valeur de leften null.

Pour contourner cela, vous avez plusieurs options. Voilà quelque:

  1. Utilisez une variable locale avec une conversion intelligente:

    val node = left
    if (node != null) {
        queue.add(node)
    }
  2. Utilisez un appel sécurisé tel que l'un des suivants:

    left?.let { node -> queue.add(node) }
    left?.let { queue.add(it) }
    left?.let(queue::add)
  3. Utilisez l' opérateur Elvis avec returnpour revenir tôt de la fonction englobante:

    queue.add(left ?: return)

    Notez que breaket continuepeut être utilisé de manière similaire pour les vérifications dans les boucles.


8
4. Pensez à une solution plus fonctionnelle à votre problème qui ne nécessite pas de variables modifiables.
Good Night Nerd Pride

1
@sak C'était une instance d'une Nodeclasse définie dans la version originale de la question qui avait un extrait de code plus compliqué avec n.leftau lieu de simplement left. J'ai mis à jour la réponse en conséquence. Merci.
mfulton26

1
@sak Les mêmes concepts s'appliquent. Vous pouvez créer un nouveau valpour chacun var, imbriquer plusieurs ?.letinstructions ou utiliser plusieurs ?: returninstructions en fonction de votre fonction. par exemple MyAsyncTask().execute(a1 ?: return, a2 ?: return, a3 ?: return). Vous pouvez également essayer l'une des solutions pour une «location de variables multiples» .
mfulton26

1
@FARID à qui faites-vous référence?
mfulton26

3
Oui, c'est sûr. Lorsqu'une variable est déclarée comme classe globale, n'importe quel thread peut modifier sa valeur. Mais dans le cas d'une variable locale (une variable déclarée à l'intérieur d'une fonction), cette variable n'est pas accessible à partir d'autres threads, donc sûr à utiliser.
Farid

31

1) Vous pouvez également utiliser lateinitSi vous vous faites votre initialisation plus tard onCreate()ou ailleurs.

Utilisez ceci

lateinit var left: Node

Au lieu de cela

var left: Node? = null

2) Et il y a une autre façon d'utiliser la !!fin de variable lorsque vous l'utilisez comme ceci

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

Qu'est ce que ça fait?
c-an

@ c-an il fait initialiser votre variable comme nulle mais attendez-vous à l'initialiser plus tard dans le code.
Radesh

Alors, n'est-ce pas pareil? @Radesh
c-an

@ c-an même avec quoi?
Radesh

1
J'ai répondu à la question ci-dessus que la conversion intelligente en 'Node' est impossible, car 'left' est une propriété modifiable qui aurait pu être modifiée à ce moment, ce code empêche cette erreur en spécifiant le type de variable. donc le compilateur n'a pas besoin de distribution intelligente
Radesh

27

Il y a une quatrième option en plus de celles de la réponse de mfulton26.

En utilisant l' ?.opérateur, il est possible d'appeler des méthodes ainsi que des champs sans traiter letou utiliser des variables locales.

Du code pour le contexte:

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

Il fonctionne avec des méthodes, des champs et toutes les autres choses que j'ai essayé de faire fonctionner.

Ainsi, afin de résoudre le problème, au lieu d'avoir à utiliser des transtypages manuels ou à l'aide de variables locales, vous pouvez utiliser ?.pour appeler les méthodes.

Pour référence, cela a été testé dans Kotlin 1.1.4-3, mais également testé dans 1.1.51et 1.1.60. Il n'y a aucune garantie que cela fonctionne sur d'autres versions, cela pourrait être une nouvelle fonctionnalité.

L'utilisation de l' ?.opérateur ne peut pas être utilisée dans votre cas car c'est une variable transmise qui est le problème. L'opérateur Elvis peut être utilisé comme alternative, et c'est probablement celui qui nécessite le moins de code. Au lieu d'utiliser continuecependant, returnpourrait également être utilisé.

L'utilisation de la coulée manuelle peut également être une option, mais ce n'est pas sans danger:

queue.add(left as Node);

Ce qui signifie que si la gauche a changé sur un autre thread, le programme plantera.


Pour autant que je comprends, le «?». L'opérateur vérifie si la variable sur son côté gauche est nulle. Dans l'exemple ci-dessus, ce serait «file d'attente». L'erreur 'smart cast impossible' fait référence au paramètre "left" passé dans la méthode "add" ... J'obtiens toujours l'erreur si j'utilise cette approche
FRR

À droite, l'erreur est activée leftet non queue. Besoin de vérifier cela, modifiera la réponse dans une minute
Zoe

4

La raison pratique pour laquelle cela ne fonctionne pas n'est pas liée aux threads. Le fait est que cela node.leftest effectivement traduit en node.getLeft().

Cet getter de propriété peut être défini comme suit:

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

Par conséquent, deux appels peuvent ne pas retourner le même résultat.




1

Pour qu'il y ait une conversion intelligente des propriétés, le type de données de la propriété doit être la classe qui contient la méthode ou le comportement auquel vous souhaitez accéder et NON que la propriété soit du type de la super classe.


par exemple sur Android

Être:

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

Solution:

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

Usage:

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

GL


1

Votre solution la plus élégante doit être:

var left: Node? = null

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

Ensuite, vous n'avez pas à définir de variable locale nouvelle et inutile, et vous n'avez pas de nouvelles assertions ou transtypages (qui ne sont pas DRY). D'autres fonctions de portée peuvent également fonctionner, alors choisissez votre préférée.


0

Essayez d'utiliser l'opérateur d'assertion non nul ...

queue.add(left!!) 

3
Dangereux. Pour la même raison, la diffusion automatique ne fonctionne pas.
Jacob Zimmerman

3
Cela peut provoquer un plantage de l'application si left est nul.
Pritam Karmakar

0

Comment je l'écrirais:

var left: Node? = null

fun show() {
     val left = left ?: return
     queue.add(left) // no error because we return if it is null
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.