简体   繁体   English

使用varargs和泛型时出现ClassCastException

[英]ClassCastException while using varargs and generics

I'm using java generics and varargs. 我正在使用java泛型和varargs。

If I use the following code, I'll get a ClassCastException , even though I'm not using casts at all. 如果我使用下面的代码,我会得到一个ClassCastException ,即使我根本不使用强制转换。

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. 更奇怪的是,如果我在Android(dalvik)上运行它,则异常中不包含堆栈跟踪,如果我将接口更改为抽象类,则异常变量e为空。

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. 如果你运行它,你将得到一个ClassCastException ,即Object无法转换为String ,堆栈跟踪指向无效的行号。 Is this a bug in Java? 这是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. 我已经在Java 7和Android API 8中测试了它。我为它做了解决方法(在doStuff -method中注释掉了),但是这样做似乎很愚蠢。 If I remove varargs ( T... ), everything works OK, but my actual implementation kinda needs it. 如果我删除varargs( T... ),一切正常,但我的实际实现有点需要它。

Stacktrace from exception is: 来自例外的Stacktrace是:

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). 在Java中使用泛型时,对象的实际类型不包含在已编译的字节码中(这称为类型擦除)。 All types become Object and casts are inserted into the compiled code to simulate typed behaviour. 所有类型都变为Object并将强制转换插入到已编译的代码中以模拟键入的行为。

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. 另外,varargs成为数组,当调用泛型varargs方法时,Java在调用它之前使用方法参数创建一个Object[]类型的数组。

Thus, your line callback.stuffDone(param); 因此,你的行callback.stuffDone(param); compiles as callback.stuffDone(new Object[] { param }); 编译为callback.stuffDone(new Object[] { param }); . However, your implementation of the callback requires an array of type String[] . 但是,您的回调实现需要一个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. Java编译器在代码中插入了一个不可见的强制转换来强制执行此类型,并且因为Object[]无法强制转换为String[] ,所以会出现异常。 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 . 一种解决方法是从Callback接口和类中完全删除泛型,用Object替换所有类型。

grahamparks answer is correct. grahamparks答案是正确的。 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. 这不是一件明智的事情......尤其是如果你不明白他们究竟是什么意思,以及他们是否可以被安全地忽略。

That's indeed due to type erasure, but the critical part here is varargs. 这确实是由于类型擦除,但这里的关键部分是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. 所以编译器实际上是创建一个Object []来打包你的参数,因此后来无效的转换。 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 :-) 但是有一个黑客围绕它:如果你足够好以传递一个表作为vararg,编译器将识别它,而不是重新打包它,因为你节省了一些工作它会让你运行你的代码:-)

Try to run after following modifications: 尝试在以下修改后运行:

public void doStuff( T[] param , Callback callback) {

and

task.doStuff( new String[]{"importantStuff"} , new Callback() {

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM