I'm using java generics and varargs.
If I use the following code, I'll get a ClassCastException
, even though I'm not using casts at all.
Stranger yet, if I run this on Android (dalvik) no stack trace is included with the exception, and if I change the interface to abstract class, the exception variable e
is empty.
The code:
public class GenericsTest {
public class Task<T> {
public void doStuff(T param, Callback<T> callback) {
// This gets called, param is String "importantStuff"
// Working workaround:
//T[] arr = (T[]) Array.newInstance(param.getClass(), 1);
//arr[0] = param;
//callback.stuffDone(arr);
// WARNING: Type safety: A generic array of T is created for a varargs parameter
callback.stuffDone(param);
}
}
public interface Callback<T> {
// WARNING: Type safety: Potential heap pollution via varargs parameter params
public void stuffDone(T... params);
}
public void run() {
Task<String> task = new Task<String>();
try {
task.doStuff("importantStuff", new Callback<String>() {
public void stuffDone(String... params) {
// This never gets called
System.out.println(params);
}});
} catch (ClassCastException e) {
// e contains "java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;"
System.out.println(e.toString());
}
}
public static void main(String[] args) {
new GenericsTest().run();
}
}
If you run this, you'll get an ClassCastException
that Object
cannot be cast to String
with stack trace pointing to invalid line number. Is this a bug in Java? I've tested it in Java 7 and Android API 8. I did workaround for it (commented out in the doStuff
-method), but it seems silly to have to do it this way. If I remove varargs (T...
), everything works OK, but my actual implementation kinda needs it.
Stacktrace from exception is:
java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
at GenericsTest$1.stuffDone(GenericsTest.java:1)
at GenericsTest$Task.doStuff(GenericsTest.java:14)
at GenericsTest.run(GenericsTest.java:26)
at GenericsTest.main(GenericsTest.java:39)
This is expected behaviour. When you use generics in Java, the actual types of the objects are not included in the compiled bytecode (this is known as type erasure). All types become Object
and casts are inserted into the compiled code to simulate typed behaviour.
Additionally, varargs become arrays, and when a generic varargs method is called, Java creates an array of type Object[]
with the method parameters before calling it.
Thus, your line callback.stuffDone(param);
compiles as callback.stuffDone(new Object[] { param });
. However, your implementation of the callback requires an array of type String[]
. The Java compiler has inserted an invisible cast in your code to enforce this typing, and because Object[]
cannot be cast to String[]
, you get an exception. The bogus line number you see is presumably because the cast doesn't appear anywhere in your code.
One workaround for this is to completely remove the generics from your Callback interface and class, replacing all types with Object
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With