I have a list of TBase
that I know is a list of TChild
, where TChild : TBase
. If I only have KClass<TChild>
, is it possible to cast List<TBase>
to List<TChild>
?
fun <TChild : BaseType> castList(list: List<BaseType>, clazz: KClass<TChild>): List<TChild> {
// is this possible?
}
I know if List
wasn't involved, I can cast an instance of TBase
to TChild
:
clazz.cast(item)
Is what I'm trying to do possible? Looking to avoid reified solutions.
CodePudding user response:
If you are really sure that it holds TChild
objects only and you won't add any other objects to it after the cast (List
is read-only, not immutable), then you can just do an unchecked cast like this:
fun <TChild : BaseType> castList(list: List<BaseType>, clazz: KClass<TChild>): List<TChild> {
@Suppress("UNCHECKED_CAST")
return list as List<TChild>
}
If you prefer to actually check types of items while casting then do:
fun <TChild : BaseType> castList(list: List<BaseType>, clazz: KClass<TChild>): List<TChild> {
return list.map { clazz.cast(it) }
}
CodePudding user response:
Yes. type assertion.
Remember, generics are figments of the compiler's imagination. The JVM doesn't know what generics is, nor does it care. Most generics is erased (is stripped out and doesn't survive the process of getting turned into a class file). The few parts that remain (generics in signatures) are treated as comments by the JVM. java.exe
ignores them entirely.
They serve solely to make kotlinc/javac generate errors, warnings, and syntax sugar. That's it.
You may make a type assertion. Be aware that these are unchecked. You're telling the compiler that you will vouch for the fact that the list in question [A] currently contains solely instances of TChild
and also [B] that in the future, for the entire duration that this code runs, nothing will change this (no code will end up adding non-TChild refs to this list.
The [B] part is obviously not at all relevant if the list is immutable.
The compiler will just take your word for it. If it turns out you were wrong, you'll get ClassCastException
on lines that contain zero casts. Because this is a bit bizarre, javac will warn you about this, but you can suppress this warning.
Looks like this in java:
List<Object> list1 = new ArrayList<Object>();
List<String> list2 = (List<String>) list1; // warning here
list1.add(5.0); // works.
list2 = (List<String>) list1; // still works. (and warns).
String a = list2.get(0); // look ma, ClassCastException without a cast!
to suppress it, @SuppressWarnings("unchecked")
on the local var decl or the method.
In kotlin it looks similar: list as List<TChild>
to cast it, @Suppress("UNCHECKED_CAST")
to suppress the warning.
An alternative is to actually iterate the list and make sure the list actually consists of the right type, throwing a ClassCastException on the spot, instead of injecting into the heap a ticking time bomb that goes off whenever some code touches the list later on:
List<Object> obj = new ArrayList<Object>();
obj.add(5.0);
List<String> strings = new ArrayList<String>();
for (Object o : obj) strings.add((String) o);
Naturally, this takes O(N)
time; any attempt to check the list is going to be O(N)
, as generics are erased so there's no way to be sure at runtime other than iterating through the lot. It's trivially similar in kotlin.