143 votes

Kotlin : comment travailler avec les castings de listes : Cast non vérifié : kotlin.collections.List<Kotlin.Any?> to kotlin.colletions.List<Waypoint>

Je veux écrire une fonction qui renvoie tous les éléments d'un fichier List qui n'est ni le premier ni le dernier élément (un point de passage). La fonction obtient un List<*> comme entrée. Un résultat ne doit être retourné 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}
}

Lors de l'émission du List<*> a List<Waypoint> je reçois l'avertissement :

Cast non vérifié : kotlin.collections.List vers kotlin.colletions.List

Je n'arrive pas à trouver un moyen de l'implémenter autrement. Quelle est la bonne façon d'implémenter cette fonction sans cet avertissement ?

265voto

hotkey Points 119

En Kotlin, il n'y a pas de moyen de vérifier les paramètres génériques au moment de l'exécution dans le cas général (comme vérifier les éléments d'un fichier de type List<T> ce qui n'est qu'un cas particulier), donc le moulage d'un type générique vers un autre avec des paramètres génériques différents donnera lieu à un avertissement, sauf si le moulage se situe dans les limites de l'espace disponible. limites de variance .

Il existe cependant différentes solutions :

  • Vous avez vérifié le type et vous êtes certain que la distribution est sûre. Étant donné cela, vous pouvez supprimer l'avertissement con @Suppress("UNCHECKED_CAST") .

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

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

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

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

    Cela créera une nouvelle liste du type désiré (évitant ainsi les cast inside non vérifiés), introduisant un peu de surcharge, mais dans le même temps, cela vous évite d'itérer à travers le fichier list et la vérification des types (dans list.foreach { ... } ), de sorte que cela ne sera pas perceptible.

  • Écrivez une fonction utilitaire qui vérifie le type et renvoie la même liste si le type est correct, encapsulant ainsi le cast (toujours non vérifié du point de vue du compilateur) en son sein :

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

    Avec l'usage :

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

8 votes

Excellente réponse ! Je choisis la solution list.filterIsInstance<Waypoint>() car je pense que c'est la plus propre.

4 votes

Notez que si vous utilisez filterIsInstance et que la liste originale contient des éléments d'un type différent, votre code les filtrera silencieusement. Parfois, c'est ce que vous voulez, mais parfois, vous préférez avoir une fonction IllegalStateException ou un jet similaire. Si c'est le cas, vous pouvez créer votre propre méthode pour la vérifier et l'utiliser : inline fun <reified R> Iterable<*>.mapAsInstance() = map { it.apply { check(this is R) } as R }

4 votes

Notez que .apply ne renvoie pas la valeur de retour de la lambda, il renvoie l'objet reçu. Vous voudrez probablement utiliser .takeIf si vous voulez que l'option renvoie un null.

16voto

Adam Kis Points 315

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

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

Cela vous donne le List<Waypoint> si tous les éléments peuvent être coulés, null sinon.

8voto

Michael Points 16659

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

Pour éviter de tels avertissements, vous devez passer un List d'objets convertibles en Waypoint . Lorsque vous utilisez * mais si vous essayez d'accéder à cette liste en tant que liste typée, vous aurez toujours besoin d'un cast et ce cast ne sera pas coché.

4voto

Samiami Jankis Points 21

J'ai fait une petite variation à la réponse de @hotkey lorsqu'elle est utilisée pour vérifier le Serializable des objets de la liste :

    @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

1voto

Ludvig Linse Points 36

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 si cela est très performant, mais au moins, il n'y a pas d'avertissement.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X