「変数」は変更可能なプロパティなので、「タイプ」へのスマートキャストは不可能です。


275

そして、Kotlinの初心者は、「なぜ次のコードがコンパイルされないのか」と質問します。

    var left: Node? = null

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

「左」はこの時点で変更されている可能性のある変更可能なプロパティであるため、「ノード」へのスマートキャストは不可能です。

それleftは可変変数ですが、明示的にチェックleft != nullleftていてタイプなNodeので、なぜそのタイプにスマートキャストできないのですか?

これをエレガントに修正するにはどうすればよいですか?:)


3
別のスレッド間のどこかで、値が再びnullに変更された可能性があります。私は他の質問への回答もそれに言及していると確信しています。
nhaarman 2017年

3
安全な呼び出しを使用して追加できます
Whymarrh

理にかなっている@nhaarmanに感謝します、Whymarrhはどうやってそれを行うことができますか?安全な呼び出しはメソッドではなくオブジェクトに対してのみであると思いました
FRR

6
のようなもの:n.left?.let { queue.add(it) }私は思いますか?
Jorn Vernee 2017年

回答:


358

実行の間left != nullqueue.add(left)、別のスレッドの値が変更されている可能性leftへとnull

これを回避するには、いくつかのオプションがあります。ここに幾つかあります:

  1. スマートキャストでローカル変数を使用します。

    val node = left
    if (node != null) {
        queue.add(node)
    }
  2. 次のような安全な呼び出しを使用します。

    left?.let { node -> queue.add(node) }
    left?.let { queue.add(it) }
    left?.let(queue::add)
  3. Elvis演算子を使用してreturn、囲み関数から早期に戻ります。

    queue.add(left ?: return)

    breakcontinueループ内のチェックに同様に使用できることに注意してください。


8
4.問題に対して、可変変数を必要としない、より機能的な解決策を考えてください。
おやすみオタクプライド

1
@sakこれは、Node質問の元のバージョンで定義されたクラスのインスタンスであり、n.left単純にではなく、より複雑なコードスニペットがありましたleft。それに応じて答えを更新しました。ありがとう。
mfulton26 2018

1
@sak同じ概念が適用されます。関数valごとvarに新しいを作成し?.letたり、いくつかのステートメントをネストしたり、いくつかの?: returnステートメントを使用したりできます。例えばMyAsyncTask().execute(a1 ?: return, a2 ?: return, a3 ?: return)「複数変数let」の解決策の1つを試すこともできます
mfulton26 2018

1
参照している@FARID?
mfulton26

3
はい、安全です。変数がクラスグローバルとして宣言されている場合、どのスレッドでもその値を変更できます。ただし、ローカル変数(関数内で宣言された変数)の場合、その変数は他のスレッドから到達できないため、安全に使用できます。
ファリッド

31

1)後で、または他の場所で確実に初期化を行うlateinit場合にも使用できます。onCreate()

これを使って

lateinit var left: Node

これの代わりに

var left: Node? = null

2)そして!!、このように使用するときに変数の終わりを使用する他の方法があります

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

それは何をするためのものか?
C-

これは、変数をnullとして初期化しますが、コードの後半で必ず初期化されることを期待しています。
Radesh

では、同じではないでしょうか。@Radesh
C-

@ c-何と同じですか?
Radesh、

1
「ノード」へのスマートキャストは不可能であるという上記の質問に答えました。「左」は、今回は変更可能な変更可能なプロパティであるため、このコードは、変数の種類を指定することでエラーを防ぎます。したがって、コンパイラはスマートキャストを必要としません
Radesh

27

mfulton26の答えにあるものに加えて、4番目のオプションがあります。

?.演算子を使用することにより、letローカル変数を処理したり使用したりせずに、フィールドだけでなくメソッドも呼び出すことができます。

コンテキストのコード:

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

メソッド、フィールド、および私がそれを機能させるために試みた他のすべてのもので動作します。

したがって、問題を解決するために、手動キャストやローカル変数を使用する代わりに、を使用?.してメソッドを呼び出すことができます。

参考までに、これはKotlin 1.1.4-3でテストされましたが、1.1.51およびでもテストされました1.1.60。他のバージョンでも動作するという保証はなく、新しい機能である可能性があります。

?.渡された変数が問題であるので、演算子の使用はあなたのケースでは使用できません。Elvisオペレーターを代替として使用することができます。これは、おそらく最も少ない量のコードを必要とするオペレーターです。continueただし、代わりに使用するreturnこともできます。

手動キャストを使用することもオプションですが、これはnullセーフではありません。

queue.add(left as Node);

別のスレッドで変更された場合、プログラムはクラッシュします。


私が理解している限り、「?.」演算子は、左側の変数がnullかどうかをチェックしています。上記の例では、 'queue'になります。エラー「スマートキャスト不可能」は、メソッド「add」に渡されるパラメーター「left」を参照しています...このアプローチを使用してもエラーが発生します
FRR

leftそうqueueです、エラーはオンであり、ではありません。これを確認する必要があります。回答を1分で編集します
Zoe

4

これが機能しない実際的な理由は、スレッドとは関係ありません。重要なのは、それnode.leftが効果的にに変換されることnode.getLeft()です。

このプロパティゲッターは次のように定義されます。

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

したがって、2つの呼び出しが同じ結果を返さない場合があります。



1

これを行う:

var left: Node? = null

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

これは、受け入れられた回答によって提供される最初のオプションのようですが、それがあなたが探しているものです。


これは「左」の変数のシャドウですか?
AFD


1

プロパティのスマートキャストが存在するためには、プロパティのデータ型は、アクセスするメソッドまたは動作を含むクラスである必要があり、プロパティがスーパークラスの型である必要はありません。


例:Android

になる:

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

解決:

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

使用法:

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

GL


1

最もエレガントなソリューションは次のとおりです。

var left: Node? = null

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

次に、新しい不要なローカル変数を定義する必要はありません。また、新しいアサーションやキャスト(DRYではない)もありません。他のスコープ関数も機能する可能性があるため、お気に入りを選択してください。


0

nullでないアサーション演算子を使用してみてください...

queue.add(left!!) 

3
危険な。同じ理由で、自動キャストは機能しません。
Jacob Zimmerman

3
leftがnullの場合、アプリがクラッシュする可能性があります。
Pritam Karmakar

0

どのように書くか:

var left: Node? = null

fun show() {
     val left = left ?: return
     queue.add(left) // no error because we return if it is null
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.