使用可变参数和泛型时出现 ClassCastException
我正在使用 java 泛型和可变参数。
如果我使用以下代码,即使我根本不使用强制转换,我也会收到 ClassCastException
。
更奇怪的是,如果我在 Android (dalvik) 上运行这个,异常中不会包含堆栈跟踪,并且如果我将接口更改为抽象类,则异常变量 e
为空。
代码:
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();
}
}
如果运行此代码,您将收到一个 ClassCastException
,表示 Object
无法转换为 String
,堆栈跟踪指向无效行号。这是 Java 中的错误吗?我已经在 Java 7 和 Android API 8 中测试了它。我为它做了解决方法(在 doStuff
方法中注释掉),但必须这样做似乎很愚蠢。如果我删除 varargs (T...
),一切正常,但我的实际实现有点需要它。
异常的堆栈跟踪是:
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)
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)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
这是预期的行为。当您在 Java 中使用泛型时,对象的实际类型不会包含在编译的字节码中(这称为类型擦除)。所有类型都变成
Object
,并且将转换插入到编译的代码中以模拟类型化行为。此外,可变参数变成数组,当调用通用可变参数方法时,Java 在调用之前会使用方法参数创建一个
Object[]
类型的数组。因此,您的行
callback.stuffDone(param);
编译为callback.stuffDone(new Object[] { param });
。但是,回调的实现需要一个String[]
类型的数组。 Java 编译器已在您的代码中插入了一个不可见的强制转换来强制执行此类型,并且由于Object[]
无法强制转换为String[]
,因此您会收到异常。您看到的虚假行号可能是因为强制转换没有出现在代码中的任何位置。解决此问题的一种方法是从回调接口和类中完全删除泛型,用
Object
替换所有类型。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 ascallback.stuffDone(new Object[] { param });
. However, your implementation of the callback requires an array of typeString[]
. The Java compiler has inserted an invisible cast in your code to enforce this typing, and becauseObject[]
cannot be cast toString[]
, 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
.格雷厄姆帕克的答案是正确的。神秘的类型转换是正常行为。它们由编译器插入,以确保应用程序在面对可能的泛型错误使用时是运行时类型安全的。
如果你遵守规则,这种类型转换总是会成功的。它失败是因为您忽略/抑制了有关不安全使用泛型的警告。这不是明智之举……特别是如果您不完全理解它们的含义,以及是否可以安全地忽略它们。
grahamparks answer is correct. The mysterious typecast is normal behaviour. They are inserted by the compiler to ensure that the application is runtime typesafe in the face of possible incorrect use of generics.
If you are playing by the rules, this typecast will always succeed. It is failing because you have ignored / suppressed the warnings about unsafe use of generics. This is not a wise thing to do ... especially if you don't understand exactly understand what they mean, and whether they can be safely ignored.
这确实是由于类型擦除造成的,但这里的关键部分是可变参数。正如已经指出的,它们是以表格的形式实现的。所以编译器实际上是创建一个 Object[] 来打包你的参数,因此后来的转换无效。
但有一个解决办法:如果你足够好地将一个表作为 vararg 传递,编译器会识别它,而不是重新打包它,因为你节省了他一些工作,它会让你运行你的代码:-)
尝试进行以下修改后运行:
public void doStuff(T[] param, Callback callback) {
和
task.doStuff(new String[]{"importantStuff “},新回调(){
That's indeed due to type erasure, but the critical part here is varargs. They are, as already noted, implemented as table. So compiler is actually creating an Object[] to pack your params and hence later invalid cast.
But there is a hack around it: if you're nice enough to pass a table as vararg, compiler will recognize it, not re-pack it and because you saved him some work it will let you run your code :-)
Try to run after following modifications:
public void doStuff(T[] param, Callback callback) {
and
task.doStuff(new String[]{"importantStuff"}, new Callback() {