Kotlin: So arbeiten Sie mit List Casts: Unchecked Cast: kotlin.collections.List <Kotlin.Any?> To kotlin.colletions.List <Waypoint>

108

Ich möchte eine Funktion schreiben, die jedes Element in einem zurückgibt List, das nicht das erste oder das letzte Element ist (ein Durchkontaktpunkt). Die Funktion erhält eine generische List<*>Eingabe. Ein Ergebnis sollte nur zurückgegeben werden, wenn die Elemente der Liste vom Typ sind 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}
}

Wenn das Gießen List<*>auf List<Waypoint>, erhalte ich die Warnung:

Deaktivierte Besetzung: kotlin.collections.List to kotlin.colletions.List

Ich kann keinen Weg finden, es anders umzusetzen. Was ist der richtige Weg, um diese Funktion ohne diese Warnung zu implementieren?

Lukas Lechner
quelle

Antworten:

190

In Kotlin gibt es im Allgemeinen keine Möglichkeit, die generischen Parameter zur Laufzeit zu überprüfen (z. B. nur die Elemente von a zu überprüfen List<T>, was nur ein Sonderfall ist). Wenn Sie also einen generischen Typ mit anderen generischen Parametern in einen anderen umwandeln, wird eine Warnung ausgelöst, es sei denn, die Besetzung liegt innerhalb der Varianzgrenzen .

Es gibt jedoch verschiedene Lösungen:

  • Sie haben den Typ überprüft und sind sich ziemlich sicher, dass die Besetzung sicher ist. Vor diesem Hintergrund können Sie die Warnung mit unterdrücken@Suppress("UNCHECKED_CAST") .

    @Suppress("UNCHECKED_CAST")
    val waypointList = list as? List<Waypoint> ?: return null
  • Verwenden Sie die .filterIsInstance<T>()Funktion, die die Elementtypen überprüft und eine Liste mit den Elementen des übergebenen Typs zurückgibt:

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

    oder dasselbe in einer Aussage:

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

    Dadurch wird eine neue Liste des gewünschten Typs erstellt (wodurch ein ungeprüftes Casting im Inneren vermieden wird), was zu einem geringen Overhead führt. Gleichzeitig erspart es Ihnen jedoch list, die Typen zu durchlaufen und zu überprüfen (in list.foreach { ... }Zeile), sodass dies nicht der Fall ist bemerkbar.

  • Schreiben Sie eine Dienstprogrammfunktion, die den Typ überprüft und dieselbe Liste zurückgibt, wenn der Typ korrekt ist, und kapseln Sie so die Besetzung (aus Sicht des Compilers noch nicht aktiviert) darin:

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

    Mit der Verwendung:

    val waypointList = list.checkItemsAre<Waypoint>() ?: return null
Hotkey
quelle
6
Gute Antwort! Ich wähle die Lösung list.filterIsInstance <Waypoint> (), weil ich denke, dass dies die sauberste Lösung ist.
Lukas Lechner
4
Beachten Sie, dass filterIsInstanceIhr Code sie stillschweigend herausfiltert , wenn Sie Elemente eines anderen Typs verwenden und die ursprüngliche Liste enthält. Manchmal ist es das, was Sie wollen, aber manchmal möchten Sie lieber einen IllegalStateExceptionoder einen ähnlichen Wurf haben. Wenn das inline fun <reified R> Iterable<*>.mapAsInstance() = map { it.apply { check(this is R) } as R }
letztere
3
Beachten Sie, dass .applyder Rückgabewert des Lambda nicht zurückgegeben wird, sondern das Empfangsobjekt. Sie möchten wahrscheinlich verwenden, .takeIfwenn die Option eine Null zurückgeben soll.
bj0
10

Um die Antwort von @ hotkey zu verbessern, ist hier meine Lösung:

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

Dies gibt Ihnen an, List<Waypoint>ob alle Gegenstände gewirkt werden können, andernfalls null.

Adam Kis
quelle
3

Bei generischen Klassen können Casts nicht überprüft werden, da die Typinformationen zur Laufzeit gelöscht werden. Sie überprüfen jedoch, ob alle Objekte in der Liste Waypoints sind, sodass Sie die Warnung einfach mit unterdrücken können @Suppress("UNCHECKED_CAST").

Um solche Warnungen zu vermeiden, müssen Sie ein ListObjekt übergeben, in das konvertiert werden kann Waypoint. Wenn Sie *diese Liste als typisierte Liste verwenden, aber versuchen, darauf zuzugreifen, benötigen Sie immer eine Besetzung, und diese Besetzung wird deaktiviert.

Michael
quelle
1

Ich habe eine kleine Änderung an @hotkey answer vorgenommen, als ich die Option Serializable to List-Objekte verwendet habe:

    @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
quelle
War auf der Suche nach einer Lösung wie dieser, aber diese Fehler:Cannot access 'Serializable': it is internal in 'kotlin.io'
Daviscodesbugs
0

Anstatt

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

Ich mache gerne

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

Ich bin mir nicht sicher, wie performant dies ist, aber zumindest keine Warnungen.

Ludvig Linse
quelle