Search code examples
listgenericscastingkotlin

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


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?


Solution

  • 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