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.