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

ListGenericsCastingKotlin

List Problem Overview


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?

List Solutions


Solution 1 - List

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

Solution 2 - List

To improve @hotkey's answer here's my solution:

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

This gives you the List<Waypoint> if all the items can be casted, null otherwise.

Solution 3 - List

In case of generic classes casts cannot be checked because type information is erased in runtime. But you check that all objects in the list are Waypoints so you can just suppress the warning with @Suppress("UNCHECKED_CAST").

To avoid such warnings you have to pass a List of objects convertible to Waypoint. When you're using * but trying to access this list as a typed list you'll always need a cast and this cast will be unchecked.

Solution 4 - List

I made a little variation to @hotkey answer when used to check 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

Solution 5 - List

Instead of

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

I like doing

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

Not sure how performant this is, but no warnings at least.

Solution 6 - List

> Kotlin ensures type safety for operations involving generics at compile time, while, at runtime, instances of generic types don't hold information about their actual type arguments. For example, List is erased to just List<*>. In general, there is no way to check whether an instance belongs to a generic type with certain type arguments at runtime.

https://kotlinlang.org/docs/typecasts.html#type-erasure-and-generic-type-checks

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionLukas LechnerView Question on Stackoverflow
Solution 1 - ListhotkeyView Answer on Stackoverflow
Solution 2 - ListAdam KisView Answer on Stackoverflow
Solution 3 - ListMichaelView Answer on Stackoverflow
Solution 4 - ListSamiami JankisView Answer on Stackoverflow
Solution 5 - ListLudvig LinseView Answer on Stackoverflow
Solution 6 - ListSabaView Answer on Stackoverflow