Kotlin: How to work with List casts: Unchecked Cast: kotlin.collections.List<Kotlin.Any?> to kotlin.colletions.List<Waypoint>

Lukas Lechner picture Lukas Lechner · Apr 12, 2016 · Viewed 48.9k times · Source

I want to write a function that returns every item in a List that is not the first or the last item (a via point). The function gets a generic List<*> as input. A result should only be returned if the elements of the list are of the 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}
}

When casting the List<*> to List<Waypoint>, I get the warning:

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

I can't figure out a way to implement it otherwise. What's the right way to implement this function without this warning?

Answer

hotkey picture hotkey · Apr 12, 2016

In Kotlin, there's no way to check the generic parameters at runtime in general case (like just checking the items of a List<T>, which is only a special case), so casting a generic type to another with different generic parameters will raise a warning unless the cast lies within variance bounds.

There are different solutions, however:

  • You have checked the type and you are quite sure that the cast is safe. Given that, you can suppress the warning with @Suppress("UNCHECKED_CAST").

    @Suppress("UNCHECKED_CAST")
    val waypointList = list as? List<Waypoint> ?: return null
    
  • Use .filterIsInstance<T>() function, which checks the item types and returns a list with the items of the passed type:

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

    or the same in one statement:

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

    This will create a new list of the desired type (thus avoiding unchecked cast inside), introducing a little overhead, but in the same time it saves you from iterating through the list and checking the types (in list.foreach { ... } line), so it won't be noticeable.

  • Write a utility function that checks the type and returns the same list if the type is correct, thus encapsulating the cast (still unchecked from the compiler's point of view) inside it:

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

    With the usage:

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