Kotlin: Come lavorare con List cast: Unchecked Cast: kotlin.collections.List <Kotlin.Any?> A kotlin.colletions.List <Waypoint>


108

Voglio scrivere una funzione che restituisca ogni elemento in un Listche non è il primo o l'ultimo elemento (un punto intermedio). La funzione ottiene un generico List<*>come input. Un risultato dovrebbe essere restituito solo se gli elementi della lista sono del tipo 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}
}

Quando lancio il List<*>a List<Waypoint>, ricevo l'avvertenza:

Cast non selezionato: kotlin.collections.List to kotlin.colletions.List

Non riesco a trovare un modo per implementarlo altrimenti. Qual è il modo giusto per implementare questa funzione senza questo avviso?

Risposte:


191

In Kotlin, non c'è modo di controllare i parametri generici in fase di esecuzione in caso generale (come il semplice controllo degli elementi di a List<T>, che è solo un caso speciale), quindi il cast di un tipo generico a un altro con parametri generici diversi genererà un avviso a meno che il cast si trova entro i limiti della varianza .

Esistono tuttavia diverse soluzioni:

  • Hai controllato il tipo e sei abbastanza sicuro che il cast sia sicuro. Detto questo, puoi sopprimere l'avviso con @Suppress("UNCHECKED_CAST").

    @Suppress("UNCHECKED_CAST")
    val waypointList = list as? List<Waypoint> ?: return null
  • Usa .filterIsInstance<T>()funzione, che controlla i tipi di elementi e restituisce un elenco con gli elementi del tipo passato:

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

    o lo stesso in una dichiarazione:

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

    Questo creerà un nuovo elenco del tipo desiderato (evitando così il cast non controllato all'interno), introducendo un po 'di overhead, ma allo stesso tempo ti evita di iterare liste controllare i tipi (in list.foreach { ... }linea), quindi non sarà evidente.

  • Scrivi una funzione di utilità che controlli il tipo e restituisca la stessa lista se il tipo è corretto, incapsulando così il cast (ancora non spuntato dal punto di vista del compilatore) al suo interno:

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

    Con l'utilizzo:

    val waypointList = list.checkItemsAre<Waypoint>() ?: return null

6
Bella risposta! Scelgo la soluzione list.filterIsInstance <Waypoint> () perché penso che sia la soluzione più pulita.
— Lukas Lechner

4
Nota che se usi filterIsInstancee l'elenco originale contiene elementi di un tipo diverso, il tuo codice li filtrerà silenziosamente. A volte questo è quello che vuoi, ma a volte potresti piuttosto avere un IllegalStateExceptionlancio o qualcosa di simile. Se il secondo è il caso, puoi creare il tuo metodo per controllare e quindi lanciare:inline fun <reified R> Iterable<*>.mapAsInstance() = map { it.apply { check(this is R) } as R }
— mfulton26

3
Si noti che .applynon restituisce il valore di ritorno di lambda, restituisce l'oggetto di ricezione. Probabilmente vuoi usare .takeIfse vuoi che l'opzione restituisca un valore nullo.
— bj0

10

Per migliorare la risposta di @ hotkey ecco la mia soluzione:

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

Questo ti dà List<Waypoint>se tutti gli elementi possono essere lanciati, altrimenti nullo.


3

In caso di classi generiche i cast non possono essere controllati perché le informazioni sul tipo vengono cancellate in runtime. Ma controlli che tutti gli oggetti nell'elenco siano in Waypointmodo da poter semplicemente sopprimere l'avviso con @Suppress("UNCHECKED_CAST").

Per evitare tali avvisi è necessario passare una serie Listdi oggetti convertibili in Waypoint. Quando utilizzi, *ma provi ad accedere a questo elenco come elenco digitato, avrai sempre bisogno di un cast e questo cast sarà deselezionato.


1

Ho apportato una piccola variazione alla risposta @hotkey quando usato per controllare Serializable in List objects:

    @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

Cannot access 'Serializable': it is internal in 'kotlin.io'
— Stavo

0

Invece di

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

Mi piace fare

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

Non sono sicuro di quanto sia performante, ma almeno nessun avvertimento.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.