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 1:
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 (inlist.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:
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:
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 Waypoint
s 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:
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