179

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?

Honza Zidek
  • 9,204
  • 4
  • 72
  • 118
Lukas Lechner
  • 7,881
  • 7
  • 40
  • 53

6 Answers6

331

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
    
Community
  • 1
  • 1
hotkey
  • 140,743
  • 39
  • 371
  • 326
  • 12
    Great Answer! I choose list.filterIsInstance() solution because I think it's the cleanest solution. – Lukas Lechner Apr 12 '16 at 12:06
  • 4
    Note that if you use [`filterIsInstance`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/filter-is-instance.html) and the original list contains elements of a different type your code will silently filter them out. Sometimes this is what you want but sometimes you might rather have an `IllegalStateException` or similar thrown. If the later is the case then you can create your own method to check and then cast: `inline fun Iterable<*>.mapAsInstance() = map { it.apply { check(this is R) } as R }` – mfulton26 Apr 12 '16 at 13:45
  • 6
    Note that `.apply` does not return the return value of the lambda, it returns the receive object. You probably want to use `.takeIf` if you want the option to return a null. – bj0 Jan 08 '18 at 18:50
  • What's the performance overhead of `filterIsInstance` over `Unchecked cast` on JVM (Android)? – zaitsman Oct 31 '22 at 02:51
22

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.

Adam Kis
  • 1,404
  • 14
  • 17
10

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.

Michael
  • 53,859
  • 22
  • 133
  • 139
6

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
  • Was looking for a solution like this, but this errors: `Cannot access 'Serializable': it is internal in 'kotlin.io'` – daviscodesbugs Sep 04 '19 at 17:24
  • solution: In android to pass a user-defined object around, your class should implements Parcelable instead of Serializable interface. – Fortran Nov 30 '20 at 09:36
1

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.

Nikolai Shevchenko
  • 7,083
  • 8
  • 33
  • 42
0

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

Saba
  • 1,208
  • 14
  • 19