Kotlin: How to work with List cast: Unchecked Cast: kotlin.collections.List <Kotlin.Any?> To kotlin.colletions.List <Waypoint>

108

Saya ingin menulis fungsi yang mengembalikan setiap item di Listyang bukan item pertama atau terakhir (titik via). Fungsi tersebut mendapat generik List<*>sebagai input. Hasil hanya boleh dikembalikan jika elemen daftar berjenis Waypoint:

fun getViaPoints(list: List<*>): List<Waypoint>? {

    list.forEach { if(it !is Waypoint ) return null }

    val waypointList = list as? List<Waypoint> ?: return null

    return waypointList.filter{ waypointList.indexOf(it) != 0 && waypointList.indexOf(it) != waypointList.lastIndex}
}

Saat mentransmisikan List<*>ke List<Waypoint>, saya mendapatkan peringatan:

Cast yang tidak dicentang: kotlin.collections.List ke kotlin.colletions.List

Saya tidak tahu cara untuk menerapkannya sebaliknya. Apa cara yang benar untuk mengimplementasikan fungsi ini tanpa peringatan ini?

Lukas Lechner
sumber

Jawaban:

191

Di Kotlin, tidak ada cara untuk memeriksa parameter generik saat runtime dalam kasus umum (seperti hanya memeriksa item dari a List<T>, yang hanya merupakan kasus khusus), jadi mentransmisikan tipe generik ke tipe lain dengan parameter generik berbeda akan memunculkan peringatan kecuali cast terletak dalam batas varians .

Namun, ada solusi yang berbeda:

  • Anda telah memeriksa jenisnya dan yakin bahwa gips aman. Mengingat itu, Anda dapat menekan peringatan tersebut dengan @Suppress("UNCHECKED_CAST").

    @Suppress("UNCHECKED_CAST")
    val waypointList = list as? List<Waypoint> ?: return null
    
  • Gunakan .filterIsInstance<T>()fungsi, yang memeriksa tipe item dan mengembalikan daftar dengan item dari tipe yang diteruskan:

    val waypointList: List<Waypoint> = list.filterIsInstance<Waypoint>()
    
    if (waypointList.size != list.size)
        return null
    

    atau sama dalam satu pernyataan:

    val waypointList = list.filterIsInstance<Waypoint>()
        .apply { if (size != list.size) return null }
    

    Ini akan membuat daftar baru dari jenis yang diinginkan (sehingga menghindari cast yang tidak dicentang di dalamnya), memperkenalkan sedikit overhead, tetapi pada saat yang sama menghemat Anda dari iterasi melalui listdan memeriksa jenis (dalam list.foreach { ... }baris), jadi tidak akan terjadi nyata.

  • Tulis fungsi utilitas yang memeriksa tipe dan mengembalikan daftar yang sama jika tipenya benar, sehingga merangkum cast (masih belum dicentang dari sudut pandang kompiler) di dalamnya:

    @Suppress("UNCHECKED_CAST")
    inline fun <reified T : Any> List<*>.checkItemsAre() =
            if (all { it is T })
                this as List<T>
            else null
    

    Dengan penggunaan:

    val waypointList = list.checkItemsAre<Waypoint>() ?: return null
hotkey
sumber
6
Jawaban Hebat! Saya memilih solusi list.filterIsInstance <Waypoint> () karena menurut saya ini adalah solusi terbersih.
Lukas Lechner
4
Perhatikan bahwa jika Anda menggunakan filterIsInstancedan daftar asli berisi elemen dari jenis yang berbeda, kode Anda akan secara diam-diam memfilternya. Terkadang ini yang Anda inginkan tetapi terkadang Anda mungkin lebih suka IllegalStateExceptiondilempar atau serupa. Jika nanti kasusnya terjadi maka Anda dapat membuat metode Anda sendiri untuk memeriksa dan kemudian inline fun <reified R> Iterable<*>.mapAsInstance() = map { it.apply { check(this is R) } as R }
mengirimkan
3
Perhatikan bahwa .applytidak mengembalikan nilai kembali lambda, itu mengembalikan objek terima. Anda mungkin ingin menggunakan .takeIfjika Anda ingin opsi mengembalikan null.
bj0
10

Untuk meningkatkan jawaban @ hotkey, inilah solusi saya:

val waypointList = list.filterIsInstance<Waypoint>().takeIf { it.size == list.size }

Ini memberi Anda List<Waypoint>jika semua item dapat dicor, nol jika tidak.

Adam Kis
sumber
3

Dalam kasus kelas generik, cetakan tidak dapat diperiksa karena informasi jenis dihapus saat runtime. Tetapi Anda memeriksa bahwa semua objek dalam daftar adalah Waypoints sehingga Anda bisa menekan peringatan dengan @Suppress("UNCHECKED_CAST").

Untuk menghindari peringatan seperti itu, Anda harus meneruskan Listobjek yang dapat dikonversi Waypoint. Saat Anda menggunakan *tetapi mencoba mengakses daftar ini sebagai daftar yang diketik, Anda akan selalu membutuhkan pemeran dan pemeran ini tidak akan dicentang.

Michael
sumber
1

Saya membuat sedikit variasi pada jawaban @hotkey ketika digunakan untuk memeriksa objek Serializable to List:

    @Suppress("UNCHECKED_CAST")
    inline fun <reified T : Any> Serializable.checkSerializableIsListOf() =
        if (this is List<*> && this.all { it is T })
          this as List<T>
        else null
Samiami Jankis
sumber
Sedang mencari solusi seperti ini, tetapi kesalahan ini:Cannot access 'Serializable': it is internal in 'kotlin.io'
daviscodesbugs
0

Dari pada

myGenericList.filter { it is AbstractRobotTurn } as List<AbstractRobotTurn>

Saya suka melakukan

myGenericList.filter { it is AbstractRobotTurn }.map { it as AbstractRobotTurn }

Tidak yakin seberapa performanya, tapi tidak ada peringatan setidaknya.

Ludvig Linse
sumber