I'm trying to understand how a ClassCastException
can be thrown from code, where as far as I understand, the type being cast to has been erased.
Example
class Foo<T : Any>(val key: String, val value: T) {
override fun toString() = "$key: $value"
}
fun <T : Foo<*>> findByKey(items: Iterable<Foo<*>>, key: String): List<T> {
return items.filter { it.key == key } as List<T>
}
fun main() {
val items = listOf(
Foo("a", "abc"),
Foo("a", 12345),
Foo("a", false)
)
val filtered = findByKey<Foo<String>>(items, "a")
println(filtered)
val values = filtered.map { it.value }
println(values)
}
Output
[a: abc, b: 12345, c: false]
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
There are a couple of sore thumbs sticking out here:
- Type
T
infindByKey
is notreified
, therefore erased. findByKey
acceptsIterable<Foo<*>
rather thanIterable<T>
(deliberately).- There's an unchecked cast to
List<T>
.
What I find interesting is that:
- Calling
findByKey<Foo<String>>(items, "a")
doesn't throwClassCastException
. - In the JVM I'd expect this call to be
findByKey(items, "a")
as<Foo<String>>
is erased. - Calling
filtered.map { it.value }
throws aClassCastException
.
I'm not looking for a solution to how to get this code working; I already know that. Rather, I'm curious to know why calling map
throws a ClassCastException
for a type that I believe to be erased?