Kotlin: Como trabalhar com List casts: Unchecked Cast: kotlin.collections.List <Kotlin.Any?> Para kotlin.colletions.List <Waypoint>

108

Quero escrever uma função que retorne todos os itens em um Listque não seja o primeiro ou o último item (um ponto de passagem). A função obtém um genérico List<*>como entrada. Um resultado só deve ser retornado se os elementos da lista forem do 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}
}

Ao lançar o List<*>para List<Waypoint>, recebo o aviso:

Cast desmarcado: kotlin.collections.List to kotlin.colletions.List

Não consigo descobrir uma maneira de implementá-lo de outra forma. Qual é a maneira certa de implementar essa função sem esse aviso?

Lukas Lechner
fonte

Respostas:

191

No Kotlin, não há como verificar os parâmetros genéricos no tempo de execução no caso geral (como apenas verificar os itens de a List<T>, que é apenas um caso especial), portanto, converter um tipo genérico para outro com parâmetros genéricos diferentes gerará um aviso, a menos que o elenco está dentro dos limites de variância .

Existem soluções diferentes, no entanto:

  • Você verificou o tipo e tem certeza de que o elenco é seguro. Sendo assim, você pode suprimir o aviso com @Suppress("UNCHECKED_CAST").

    @Suppress("UNCHECKED_CAST")
    val waypointList = list as? List<Waypoint> ?: return null
    
  • Use a .filterIsInstance<T>()função, que verifica os tipos de itens e retorna uma lista com os itens do tipo passado:

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

    ou o mesmo em uma declaração:

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

    Isso criará uma nova lista do tipo desejado (evitando assim o cast não verificado dentro), introduzindo um pouco de overhead, mas ao mesmo tempo evita que você precise iterar liste verificar os tipos (em list.foreach { ... }linha), por isso não será perceptível.

  • Escreva uma função de utilidade que verifica o tipo e retorna a mesma lista se o tipo estiver correto, encapsulando o elenco (ainda não verificado do ponto de vista do compilador) dentro dele:

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

    Com o uso:

    val waypointList = list.checkItemsAre<Waypoint>() ?: return null
tecla de atalho
fonte
6
Ótima resposta! Eu escolhi a solução list.filterIsInstance <Waypoint> () porque acho que é a solução mais limpa.
Lukas Lechner,
4
Observe que se você usar filterIsInstancee a lista original contiver elementos de um tipo diferente, seu código os filtrará silenciosamente. Às vezes é isso que você deseja, mas às vezes você pode preferir que um IllegalStateExceptionou semelhante seja lançado. Se o último for o caso, você pode criar seu próprio método para verificar e, em seguida, lançar:inline fun <reified R> Iterable<*>.mapAsInstance() = map { it.apply { check(this is R) } as R }
mfulton26
3
Observe que .applynão retorna o valor de retorno do lambda, ele retorna o objeto de recebimento. Você provavelmente deseja usar .takeIfse quiser a opção de retornar um valor nulo.
bj0
10

Para melhorar a resposta de @ hotkey, aqui está minha solução:

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

Isso dá a você List<Waypoint>se todos os itens podem ser lançados, caso contrário, null.

Adam Kis
fonte
3

No caso de classes genéricas, os casts não podem ser verificados porque as informações do tipo são apagadas em tempo de execução. Mas você verifica se todos os objetos na lista são Waypoints para que possa simplesmente suprimir o aviso com @Suppress("UNCHECKED_CAST").

Para evitar tais avisos, você deve passar uma lista Listde objetos conversíveis para Waypoint. Quando você estiver usando, *mas tentando acessar esta lista como uma lista digitada, você sempre precisará de um elenco e este elenco estará desmarcado.

Michael
fonte
1

Fiz uma pequena variação na resposta @hotkey quando usado para marcar Serializable to 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
Samiami Jankis
fonte
Estava procurando uma solução como esta, mas estes erros:Cannot access 'Serializable': it is internal in 'kotlin.io'
daviscodesbugs
0

Ao invés de

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

Eu gosto de fazer

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

Não tenho certeza de como é o desempenho, mas sem avisos, pelo menos.

Ludvig Linse
fonte