Kotlin: Cómo trabajar con List casts: Sin marcar Cast: kotlin.collections.List <Kotlin.Any?> To kotlin.colletions.List <Waypoint>

108

Quiero escribir una función que devuelva todos los elementos de un elemento Listque no sea el primero ni el último (un punto intermedio). La función obtiene un genérico List<*>como entrada. Solo se debe devolver un resultado si los elementos de la lista son 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}
}

Cuando lanzo el List<*>to List<Waypoint>, recibo la advertencia:

Reparto sin marcar: kotlin.collections.List a kotlin.colletions.List

No puedo encontrar la manera de implementarlo de otra manera. ¿Cuál es la forma correcta de implementar esta función sin esta advertencia?

Lukas Lechner
fuente

Respuestas:

191

En Kotlin, no hay forma de verificar los parámetros genéricos en tiempo de ejecución en el caso general (como simplemente verificar los elementos de a List<T>, que es solo un caso especial), por lo que la conversión de un tipo genérico a otro con diferentes parámetros genéricos generará una advertencia a menos que el el reparto se encuentra dentro de los límites de la varianza .

Sin embargo, existen diferentes soluciones:

  • Ha comprobado el tipo y está seguro de que el yeso es seguro. Dado eso, puede suprimir la advertencia con @Suppress("UNCHECKED_CAST").

    @Suppress("UNCHECKED_CAST")
    val waypointList = list as? List<Waypoint> ?: return null
    
  • Use la .filterIsInstance<T>()función, que verifica los tipos de elementos y devuelve una lista con los elementos del tipo pasado:

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

    o lo mismo en una declaración:

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

    Esto creará una nueva lista del tipo deseado (evitando así el lanzamiento sin marcar en el interior), introduciendo un poco de sobrecarga, pero al mismo tiempo le ahorra tener que iterar listy verificar los tipos (en list.foreach { ... }línea), por lo que no será perceptible.

  • Escriba una función de utilidad que verifique el tipo y devuelva la misma lista si el tipo es correcto, encapsulando así la conversión (aún sin marcar desde el punto de vista del compilador) dentro de ella:

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

    Con el uso:

    val waypointList = list.checkItemsAre<Waypoint>() ?: return null
tecla de acceso rápido
fuente
6
¡Gran respuesta! Elijo la solución list.filterIsInstance <Waypoint> () porque creo que es la solución más limpia.
Lukas Lechner
4
Tenga en cuenta que si usa filterIsInstancey la lista original contiene elementos de un tipo diferente, su código los filtrará silenciosamente. A veces, esto es lo que quieres, pero a veces es posible que prefieras tener uno IllegalStateExceptiono algo similar. Si el último es el caso, puede crear su propio método para verificar y luego emitir:inline fun <reified R> Iterable<*>.mapAsInstance() = map { it.apply { check(this is R) } as R }
mfulton26
3
Tenga en cuenta que .applyno devuelve el valor de retorno de la lambda, devuelve el objeto de recepción. Probablemente desee usar .takeIfsi desea que la opción devuelva un valor nulo.
bj0
10

Para mejorar la respuesta de @ hotkey, aquí está mi solución:

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

Esto le da List<Waypoint>si todos los elementos se pueden convertir, nulo en caso contrario.

Adam Kis
fuente
3

En el caso de clases genéricas, las conversiones no se pueden verificar porque la información de tipo se borra en tiempo de ejecución. Pero verifica que todos los objetos de la lista sean Waypoints para poder suprimir la advertencia con @Suppress("UNCHECKED_CAST").

Para evitar tales advertencias, debe pasar una serie Listde objetos convertibles a Waypoint. Cuando esté usando, *pero intentando acceder a esta lista como una lista escrita, siempre necesitará un elenco y este elenco no estará marcado.

Miguel
fuente
1

Hice una pequeña variación en la respuesta de @hotkey cuando se usa para verificar Serializable to List objetos:

    @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
fuente
Estaba buscando una solución como esta, pero este error:Cannot access 'Serializable': it is internal in 'kotlin.io'
daviscodesbugs
0

En vez de

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

me gusta hacer

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

No estoy seguro de cuán eficiente es esto, pero al menos no hay advertencias.

Ludvig Linse
fuente