Kotlin: Comment travailler avec les castes de liste: Cast non coché: kotlin.collections.List <Kotlin.Any?> Vers kotlin.colletions.List <Waypoint>

108

Je veux écrire une fonction qui renvoie chaque élément dans a Listqui n'est pas le premier ou le dernier élément (un point intermédiaire). La fonction obtient un générique List<*>en entrée. Un résultat ne doit être renvoyé que si les éléments de la liste sont du type 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}
}

Lorsque je lance le List<*>vers List<Waypoint>, je reçois l'avertissement:

Diffusion non cochée: kotlin.collections.List vers kotlin.colletions.List

Je ne peux pas trouver un moyen de le mettre en œuvre autrement. Quelle est la bonne façon d'implémenter cette fonction sans cet avertissement?

Lukas Lechner
la source

Réponses:

191

Dans Kotlin, il n'y a aucun moyen de vérifier les paramètres génériques au moment de l'exécution dans un cas général (comme vérifier simplement les éléments de a List<T>, qui n'est qu'un cas spécial), donc le cast d'un type générique en un autre avec des paramètres génériques différents lèvera un avertissement à moins que le le cast se situe dans les limites de la variance .

Il existe cependant différentes solutions:

  • Vous avez vérifié le type et vous êtes sûr que le plâtre est sûr. Compte tenu de cela, vous pouvez supprimer l'avertissement avec @Suppress("UNCHECKED_CAST").

    @Suppress("UNCHECKED_CAST")
    val waypointList = list as? List<Waypoint> ?: return null
    
  • Utilisez la .filterIsInstance<T>()fonction, qui vérifie les types d'éléments et retourne une liste avec les éléments du type passé:

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

    ou la même chose dans une seule déclaration:

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

    Cela créera une nouvelle liste du type souhaité (évitant ainsi un cast non vérifié à l'intérieur), introduisant un peu de surcharge, mais en même temps, cela vous évitera d'itérer listet de vérifier les types (en list.foreach { ... }ligne), donc ce ne sera pas perceptible.

  • Ecrivez une fonction utilitaire qui vérifie le type et retourne la même liste si le type est correct, encapsulant ainsi le cast (toujours non coché du point de vue du compilateur) à l'intérieur:

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

    Avec l'utilisation:

    val waypointList = list.checkItemsAre<Waypoint>() ?: return null
touche de raccourci
la source
6
Très bonne réponse! Je choisis la solution list.filterIsInstance <Waypoint> () car je pense que c'est la solution la plus propre.
Lukas Lechner
4
Notez que si vous utilisez filterIsInstanceet que la liste d'origine contient des éléments d'un type différent, votre code les filtrera silencieusement. Parfois, c'est ce que vous voulez, mais parfois vous pourriez plutôt avoir un IllegalStateExceptionjet ou similaire. Si ce dernier est le cas, vous pouvez créer votre propre méthode pour vérifier, puis lancer:inline fun <reified R> Iterable<*>.mapAsInstance() = map { it.apply { check(this is R) } as R }
mfulton26
3
Notez que .applyne renvoie pas la valeur de retour du lambda, il renvoie l'objet de réception. Vous voudrez probablement l'utiliser .takeIfsi vous souhaitez que l'option renvoie une valeur nulle.
bj0
10

Pour améliorer la réponse de @ hotkey, voici ma solution:

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

Cela vous donne List<Waypoint>si tous les éléments peuvent être castés, null dans le cas contraire.

Adam Kis
la source
3

Dans le cas de classes génériques, les casts ne peuvent pas être vérifiés car les informations de type sont effacées lors de l'exécution. Mais vous vérifiez que tous les objets de la liste sont des Waypoints afin que vous puissiez simplement supprimer l'avertissement avec @Suppress("UNCHECKED_CAST").

Pour éviter de tels avertissements, vous devez passer un Listdes objets convertibles en Waypoint. Lorsque vous utilisez *mais essayez d'accéder à cette liste sous forme de liste saisie, vous aurez toujours besoin d'une distribution et cette distribution sera décochée.

Michael
la source
1

J'ai fait une petite variation à la réponse @hotkey lorsque je suis utilisé pour vérifier les objets 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
la source
Je cherchais une solution comme celle-ci, mais ces erreurs:Cannot access 'Serializable': it is internal in 'kotlin.io'
daviscodesbugs
0

Au lieu de

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

J'aime faire

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

Je ne sais pas à quel point c'est performant, mais au moins aucun avertissement.

Ludvig Linse
la source