Eine intelligente Umwandlung in "Typ" ist nicht möglich, da "Variable" eine veränderbare Eigenschaft ist, die zu diesem Zeitpunkt möglicherweise geändert wurde


275

Und der Kotlin-Neuling fragt: "Warum wird der folgende Code nicht kompiliert?":

    var left: Node? = null

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

Eine intelligente Umwandlung in "Knoten" ist nicht möglich, da "links" eine veränderbare Eigenschaft ist, die zu diesem Zeitpunkt möglicherweise geändert wurde

Ich verstehe , dass leftes sich um eine veränderbare Variable handelt, aber ich überprüfe dies explizit left != nullund bin leftvom Typ. NodeWarum kann sie nicht intelligent in diesen Typ umgewandelt werden?

Wie kann ich das elegant beheben? :) :)


3
Irgendwo dazwischen könnte ein anderer Thread den Wert wieder auf null geändert haben. Ich bin mir ziemlich sicher, dass die Antworten auf die anderen Fragen dies ebenfalls erwähnen.
Nhaarman

3
Sie könnten einen sicheren Anruf verwenden, um hinzuzufügen
Whymarrh

danke @nhaarman das macht Sinn, Whymarrh wie kann man das machen? Ich dachte, sichere Aufrufe wären nur für Objekte, nicht für Methoden
FRR

6
So etwas wie: n.left?.let { queue.add(it) }Ich denke?
Jorn Vernee

Antworten:


356

Zwischen der Ausführung von left != nullund queue.add(left)einem anderen Thread könnte sich der Wert von leftauf geändert haben null.

Um dies zu umgehen, haben Sie mehrere Möglichkeiten. Hier sind einige:

  1. Verwenden Sie eine lokale Variable mit Smart Cast:

    val node = left
    if (node != null) {
        queue.add(node)
    }
  2. Verwenden Sie einen sicheren Anruf wie einen der folgenden:

    left?.let { node -> queue.add(node) }
    left?.let { queue.add(it) }
    left?.let(queue::add)
  3. Verwenden Sie den Elvis-Operator mit return, um frühzeitig von der umschließenden Funktion zurückzukehren:

    queue.add(left ?: return)

    Beachten Sie, dass breakund continuein ähnlicher Weise für Überprüfungen innerhalb von Schleifen verwendet werden kann.


8
4. Überlegen Sie sich eine funktionalere Lösung für Ihr Problem, für die keine veränderlichen Variablen erforderlich sind.
Gute Nacht Nerd Pride

1
@sak Es war eine Instanz einer NodeKlasse, die in der Originalversion der Frage definiert war und ein komplizierteres Code-Snippet enthieltn.left anstelle von einfachleft . Ich habe die Antwort entsprechend aktualisiert. Vielen Dank.
mfulton26

1
@sak Die gleichen Konzepte gelten. Sie können valfür jede eine neue erstellen var, mehrere ?.letAnweisungen verschachteln oder ?: returnje nach Ihrer Funktion mehrere Anweisungen verwenden. zB MyAsyncTask().execute(a1 ?: return, a2 ?: return, a3 ?: return). Sie können auch eine der Lösungen für eine "Mehrfachvariablen-Vermietung" ausprobieren. .
mfulton26

1
@FARID auf wen bezieht sich?
mfulton26

3
Ja, es ist sicher. Wenn eine Variable als Klasse global deklariert wird, kann jeder Thread ihren Wert ändern. Im Fall einer lokalen Variablen (einer in einer Funktion deklarierten Variablen) ist diese Variable jedoch nicht von anderen Threads aus erreichbar und daher sicher zu verwenden.
Farid

31

1) Sie können auch verwenden, lateinitwenn Sie Ihre Initialisierung später oder anderswo sicher durchführenonCreate() .

Benutze das

lateinit var left: Node

An Stelle von

var left: Node? = null

2) Und es gibt eine andere Möglichkeit, das !!Ende der Variablen zu verwenden, wenn Sie es so verwenden

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

was tut es?
c-an

@ c-an Es lässt Ihre Variable als null initialisieren, aber erwarten Sie, dass sie später im Code initialisiert wird.
Radesh

Ist es dann nicht dasselbe? @ Radesh
c-an

@ c-an gleich mit was?
Radesh

1
Ich beantwortete die obige Frage, dass Smart Casting in 'Node' unmöglich ist, da 'left' eine veränderbare Eigenschaft ist, die zu diesem Zeitpunkt geändert werden könnte. Dieser Code verhindert diesen Fehler, indem er die Art der Variablen angibt. Compiler braucht also keine Smart Cast
Radesh

27

Zusätzlich zu denen in der Antwort von mfulton26 gibt es eine vierte Option.

Mit dem ?.Operator können sowohl Methoden als auch Felder aufgerufen werden, ohne letlokale Variablen zu behandeln oder zu verwenden.

Code für den Kontext:

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

Es funktioniert mit Methoden, Feldern und all den anderen Dingen, die ich versucht habe, damit es funktioniert.

Um das Problem zu lösen, können Sie ?.die Methoden aufrufen , anstatt manuelle Umwandlungen oder lokale Variablen verwenden zu müssen.

Als Referenz wurde dies in Kotlin getestet 1.1.4-3, aber auch in 1.1.51und 1.1.60. Es gibt keine Garantie, dass es auf anderen Versionen funktioniert, es könnte eine neue Funktion sein.

Die Verwendung des ?.Operators kann in Ihrem Fall nicht verwendet werden, da es sich um eine übergebene Variable handelt, die das Problem darstellt. Der Elvis-Operator kann als Alternative verwendet werden und erfordert wahrscheinlich die geringste Menge an Code. Anstatt jedoch zu verwenden continue,return könnte auch verwendet werden.

Die Verwendung von manuellem Casting könnte ebenfalls eine Option sein, dies ist jedoch nicht null sicher:

queue.add(left as Node);

Das heißt, wenn sich left in einem anderen Thread geändert hat , stürzt das Programm ab.


Soweit ich weiß, ist das '?' Der Operator prüft, ob die Variable auf der linken Seite null ist. Im obigen Beispiel wäre es 'Warteschlange'. Der Fehler 'Smart Cast unmöglich' bezieht sich auf den Parameter "left", der an die Methode "add" übergeben wird ... Ich erhalte immer noch den Fehler, wenn ich diesen Ansatz verwende
FRR

Richtig, der Fehler ist eingeschaltet leftund nicht queue. Müssen Sie dies überprüfen, wird die Antwort in einer Minute bearbeiten
Zoe

4

Der praktische Grund, warum dies nicht funktioniert, hängt nicht mit Threads zusammen. Der Punkt ist, dass node.lefteffektiv in übersetzt wird node.getLeft().

Dieser Eigenschafts-Getter kann wie folgt definiert werden:

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

Daher geben zwei Aufrufe möglicherweise nicht das gleiche Ergebnis zurück.




1

Damit es eine intelligente Umwandlung der Eigenschaften gibt, muss der Datentyp der Eigenschaft die Klasse sein, die die Methode oder das Verhalten enthält, auf die Sie zugreifen möchten, und NICHT, dass die Eigenschaft vom Typ der Superklasse ist.


zB auf Android

Sein:

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

Lösung:

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

Verwendungszweck:

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

GL


1

Ihre eleganteste Lösung muss sein:

var left: Node? = null

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

Dann müssen Sie keine neue und unnötige lokale Variable definieren, und Sie haben keine neuen Assertions oder Casts (die nicht DRY sind). Andere Bereichsfunktionen könnten ebenfalls funktionieren, wählen Sie also Ihren Favoriten.


0

Versuchen Sie es mit dem Nicht-Null-Assertionsoperator ...

queue.add(left!!) 

3
Gefährlich. Aus dem gleichen Grund funktioniert das Auto-Casting nicht.
Jacob Zimmerman

3
Es kann zu einem Absturz der App führen, wenn left null ist.
Pritam Karmakar

0

Wie ich es schreiben würde:

var left: Node? = null

fun show() {
     val left = left ?: return
     queue.add(left) // no error because we return if it is null
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.