Smart cast ke 'Type' tidak mungkin, karena 'variabel' adalah properti yang bisa berubah yang bisa diubah saat ini

275

Dan pemula Kotlin bertanya, "mengapa kode berikut tidak dapat dikompilasi?":

    var left: Node? = null

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

Pemain pintar ke 'Node' tidak mungkin, karena 'kiri' adalah properti yang bisa berubah yang bisa diubah saat ini

Saya mendapatkan itu leftadalah variabel yang bisa berubah-ubah, tapi saya secara eksplisit memeriksa left != nulldan leftbertipe Nodejadi mengapa tidak bisa dicor-pintar ke tipe itu?

Bagaimana saya bisa memperbaikinya dengan elegan? :)

FRR
sumber
3
Di suatu tempat di antara utas yang berbeda dapat mengubah nilai menjadi nol lagi. Saya cukup yakin jawaban atas pertanyaan lain menyebutkan itu juga.
nhaarman
3
Anda bisa menggunakan panggilan aman untuk menambahkan
Whymarrh
terima kasih @nhaarman itu masuk akal, Whymarrh bagaimana bisa melakukan itu? Saya pikir panggilan aman hanya untuk objek bukan metode
FRR
6
Sesuatu seperti: n.left?.let { queue.add(it) }saya pikir?
Jorn Vernee

Jawaban:

358

Antara eksekusi left != nulldan queue.add(left)utas lainnya dapat mengubah nilai leftmenjadi null.

Untuk mengatasinya Anda memiliki beberapa opsi. Inilah beberapa:

  1. Gunakan variabel lokal dengan smart cast:

    val node = left
    if (node != null) {
        queue.add(node)
    }
    
  2. Gunakan panggilan aman seperti salah satu dari yang berikut:

    left?.let { node -> queue.add(node) }
    left?.let { queue.add(it) }
    left?.let(queue::add)
    
  3. Gunakan operator Elvis dengan returnuntuk kembali lebih awal dari fungsi melampirkan:

    queue.add(left ?: return)

    Perhatikan bahwa breakdan continuedapat digunakan secara serupa untuk pemeriksaan dalam loop.

mfulton26
sumber
8
4. Pikirkan solusi yang lebih fungsional untuk masalah Anda yang tidak memerlukan variabel yang bisa berubah.
Selamat Malam Nerd Pride
1
@sak Ini adalah instance dari Nodekelas yang didefinisikan dalam versi asli dari pertanyaan yang memiliki potongan kode yang lebih rumit n.leftdaripada hanya left. Saya telah memperbarui jawabannya. Terima kasih.
mfulton26
1
@sak Konsep yang sama berlaku. Anda dapat membuat yang baru valuntuk masing-masing var, membuat beberapa ?.letpernyataan, atau menggunakan beberapa ?: returnpernyataan tergantung pada fungsi Anda. mis MyAsyncTask().execute(a1 ?: return, a2 ?: return, a3 ?: return). Anda juga dapat mencoba salah satu solusi untuk "membiarkan banyak variabel" .
mfulton26
1
@FARID siapa yang dimaksud?
mfulton26
3
Ya, aman. Ketika suatu variabel dinyatakan sebagai kelas global, utas apa pun dapat mengubah nilainya. Tetapi dalam kasus variabel lokal (variabel dideklarasikan di dalam suatu fungsi), variabel itu tidak dapat dijangkau dari utas lainnya, jadi aman untuk digunakan.
Farid
31

1) Anda juga dapat menggunakan lateinitJika Anda yakin melakukan inisialisasi nanti onCreate()atau di tempat lain.

Gunakan ini

lateinit var left: Node

Alih-alih ini

var left: Node? = null

2) Dan ada cara lain yang menggunakan !!variabel akhir saat Anda menggunakannya seperti ini

queue.add(left!!) // add !!
Radesh
sumber
apa fungsinya?
c-an
@ c-an itu membuat variabel Anda menginisialisasi sebagai nol tetapi berharap untuk menginisialisasi nanti dalam kode.
Radesh
Lalu, bukankah itu sama? @ Sadesh
c-an
@ c-sama dengan apa?
Radesh
1
saya menjawab pertanyaan di atas yang Smart cast ke 'Node' tidak mungkin, karena 'kiri' adalah properti yang bisa berubah yang dapat diubah saat ini kode ini mencegah kesalahan itu dengan menentukan jenis variabel. jadi kompiler tidak perlu untuk pemain pintar
Radesh
27

Ada opsi keempat selain yang ada di jawaban mfulton26.

Dengan menggunakan ?.operator dimungkinkan untuk memanggil metode serta bidang tanpa berurusan dengan letatau menggunakan variabel lokal.

Beberapa kode untuk konteks:

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

Ini bekerja dengan metode, bidang dan semua hal lain yang saya coba untuk membuatnya berfungsi.

Jadi untuk menyelesaikan masalah, alih-alih harus menggunakan gips manual atau menggunakan variabel lokal, Anda dapat menggunakan ?.untuk memanggil metode.

Untuk referensi, ini diuji di Kotlin 1.1.4-3, tetapi juga diuji di 1.1.51dan 1.1.60. Tidak ada jaminan itu berfungsi pada versi lain, itu bisa menjadi fitur baru.

Menggunakan ?.operator tidak dapat digunakan dalam kasus Anda karena variabel yang diteruskan itulah masalahnya. Operator Elvis dapat digunakan sebagai alternatif, dan mungkin salah satu yang membutuhkan jumlah kode paling sedikit. Alih-alih menggunakan continuesekalipun, returnbisa juga digunakan.

Menggunakan casting manual juga bisa menjadi pilihan, tetapi ini bukan nol aman:

queue.add(left as Node);

Berarti jika kiri telah berubah pada utas yang berbeda, program akan macet.

Zoe
sumber
Sejauh yang saya mengerti, '?.' Operator memeriksa apakah variabel di sisi kirinya adalah nol .. Pada contoh di atas akan menjadi 'antrian'. Kesalahan 'smart cast impossible' mengacu pada parameter "left" yang diteruskan ke metode "add" ... Saya masih mendapatkan kesalahan jika saya menggunakan pendekatan ini
FRR
Benar, kesalahannya hidup leftdan tidak queue. Perlu memeriksa ini, akan mengedit jawabannya dalam satu menit
Zoe
4

Alasan praktis mengapa ini tidak berhasil tidak terkait dengan utas. Intinya adalah yang node.leftditerjemahkan secara efektif ke dalam node.getLeft().

Pencari properti ini dapat didefinisikan sebagai:

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

Karenanya dua panggilan mungkin tidak mengembalikan hasil yang sama.

Roland Illig
sumber
2

Ubah var left: Node? = nullke lateinit var left: Node. Masalah terpecahkan.

Manohor Mohammed
sumber
1

Melakukan hal ini:

var left: Node? = null

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

Yang tampaknya menjadi opsi pertama yang disediakan oleh jawaban yang diterima, tetapi itulah yang Anda cari.

EpicPandaForce
sumber
Ini membayangi variabel "kiri"?
AFD
Yang benar-benar oke. Lihat reddit.com/r/androiddev/comments/fdp2zq/…
EpicPandaForce
1

Agar ada Smart Cast dari properti, tipe data dari properti harus kelas yang berisi metode atau perilaku yang ingin Anda akses dan BUKAN bahwa properti itu dari tipe kelas super.


misalnya di Android

Menjadi:

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

Larutan:

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

Pemakaian:

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

GL

Braian Coronel
sumber
1

Solusi Anda yang paling elegan harus:

var left: Node? = null

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

Maka Anda tidak perlu mendefinisikan variabel lokal baru dan tidak perlu, dan Anda tidak memiliki pernyataan atau gips baru (yang bukan KERING). Fungsi lingkup lain juga bisa berfungsi jadi pilih favorit Anda.

Simon Jacobs
sumber
0

Coba gunakan operator pernyataan tidak-nol ...

queue.add(left!!) 
Bikeboy
sumber
3
Berbahaya. Untuk alasan yang sama, auto-casting tidak berfungsi.
Jacob Zimmerman
3
Ini dapat menyebabkan aplikasi mogok jika dibiarkan nol.
Pritam Karmakar
0

Bagaimana saya akan menulisnya:

var left: Node? = null

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