在使用可变参数和泛型时发生ClassCastException [英] ClassCastException while using varargs and generics

查看:141
本文介绍了在使用可变参数和泛型时发生ClassCastException的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



如果我使用下面的代码,我会得到一个 ClassCastException

奇怪的是,如果我在Android(dalvik)上运行这个函数,那么异常中不会包含任何堆栈跟踪,如果我将接口更改为抽象类,则异常变量 e 为空。



代码:

  public class GenericsTest {
public class Task< T> {
public void doStuff(T param,Callback< T> callback){
//这被调用,param是StringimportantStuff

//工作解决方法:
// T [] arr =(T [])Array.newInstance(param.getClass(),1);
// arr [0] = param;
//callback.stuffDone (arr);

//警告:类型安全:为可变参数
callback.stuffDone(param)创建T的通用数组;
}
}

public interface Callback< T> {
//警告:类型安全:通过varargs参数params可能造成堆污染
public void stuffDone(T ... params);
}

public void run(){
任务< String> task = new Task< String>();
try {
task.doStuff(importantStuff,new Callback< String>(){
public void stuffDone(String ... params){
//永远不会获取称为
System.out.println(params);
}});
} catch(ClassCastException e){
// e包含java.lang.ClassCastException:[Ljava.lang.Object;不能转换为[Ljava.lang.String;
System.out.println(e.toString());



public static void main(String [] args){
new GenericsTest()。run();




$ b如果你运行这个, code> ClassCastException
Object 不能转换为字符串无效的行号。这是Java中的错误吗?我已经在Java 7和Android API 8中进行了测试。我为它做了解决方法(在 doStuff -method中注释掉了),但似乎很愚蠢这条路。如果我删除可变参数( T ... ),一切正常,但是我的实际实现有点需要它。

异常堆栈跟踪是:

  java.lang.ClassCastException:[Ljava.lang.Object;不能转换为[Ljava.lang.String; (GenericsTest.java:1)
GenericsTest $ $ $ b $ GenericsTest.java:
GenericsTest.java:
(GenericsTest.java:26)$ b $ GenericsTest.main(GenericsTest.java:39)


解决方案

<这是预期的行为。在Java中使用泛型时,对象的实际类型不包含在已编译的字节码中(这称为类型擦除)。所有类型变成 Object ,并且强制转换插入到编译后的代码中以模拟类型化行为。

另外,可变参数变成了数组,并且当调用通用的varargs方法时,Java在调用它之前用方法参数创建一个类型为 Object [] 的数组。



因此,你的行 callback.stuffDone(param); 编译为 callback.stuffDone(new Object [] {param}); 。但是,您的回调实现需要 String [] 类型的数组。 Java编译器在您的代码中插入了一个不可见的强制转换来强制执行此类输入,并且因为 Object [] 不能转换为 String [] ,你会得到一个异常。您看到的虚假行号大概是因为演员没有出现在您的代码中的任何位置。



解决方法是彻底删除Callback界面中的泛型,类,用 Object 替换所有类型。


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.

这篇关于在使用可变参数和泛型时发生ClassCastException的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆