使用Java-8时varargs中的ClassCastException

时间:2019-12-18 06:48:23

标签: java generics exception java-8

以下代码在Go Build上工作正常,但是在我使用m2()时抛出ClassCastException

m1()m1之间的唯一区别是参数的数量。

m2

我的问题是-使用泛型时,可变参数是否不能与单个参数一起使用?

PS :这与ClassCastException using Generics and Varargs

不相关

4 个答案:

答案 0 :(得分:10)

让我们跳过一个事实,您现在暂时忽略了未经检查的强制转换警告,并尝试了解发生这种情况的原因。

在此声明中:

Test.m3(Test.m4("1"));

有一种推断类型,即m4的返回类型。如果要在m3调用上下文之外使用它,例如:

Test.m4("1"); // T is Object

T被推断为Object。可以使用类型见证程序来强制编译器使用给定类型:

Test.<String>("1"); // T is String

...或在分配上下文中使用表达式:

String resString = Test.m4("1"); // T is String
Integer resInt = Test.m4("1"); // T is Integer <-- see the problem?

...或在调用上下文中:

Integer.parseInt(Test.m4("1")); // T is String
Long.toString(Test.m4("1")); // T is Long

现在,回到Test.m3(Test.m4("1"));:我找不到对此的引用,但是我相信编译器被迫将T解析为m3的参数类型是Object[]。这意味着必须T的参数类型一致的m3因此被解析为Object[],就好像您指定了泛型一样类型为:

Test.m3(Test.<Object[]>m4("1")); // this is what is happening

现在,由于m4未返回Object[]m3正在接收String,这导致了不可避免的ClassCastException

如何解决?

解决此问题的第一种方法是为m4指定正确的类型参数:

Test.m3(Test.<String>m4("1")); 

这样,Stringm4的返回类型,并且m3是用单个String对象调用的(对于Object... var-arg ),就像您写过一样:

String temp = m4("1");
m3(temp);

第二种方法是在@Ravindra Ranwala删除的答案中提出的。我认为,这可以归结为注意编译器警告:

public static <T> T m4(Object s) {
    return (T) s; // unchecked cast
} 

未经检查的强制转换警告只是告诉您编译器(和运行时)将不强制类型兼容,这仅仅是因为T不知道您强制转换的位置。以下版本是类型安全的,但也使编译器使用String作为m4的返回类型以及m3的参数类型:

public static <T> T m4(T s) {
    return s;
}

有了这个,m3(m4("1"));仍然使用Object...作为m3的参数类型,同时保持String的返回类型m4(即字符串)值用作Object数组的第一个元素)。

答案 1 :(得分:5)

因为在方法实现中,数组仅为read and nothing is stored in the array。但是,如果方法将某些内容存储在数组中,则它可能会尝试在数组中存储外来对象,例如将HashMap<Long,Long>放入HashMap<String,String>[]中。编译器和运行时系统都无法阻止它。

这里是另一个example,说明了忽略与变量参数列表结合使用的有关数组构造的警告的潜在危险。

static <T> T[] method_1(T t1, T t2) { 
            return method_2(t1, t2);                       // unchecked warning 
        } 
        static <T> T[] method_2( T... args) { 
            return args; 
        } 
        public static void main(String... args) { 
            String[] strings = method_1("bad", "karma");     // ClassCastException 
        } 
  

警告:[unchecked]为类型T []的未经检查的通用数组创建   varargs参数

        return method_2(t1, t2); 

如前面的示例所示,数组的组件类型是不可更改的,由于类型擦除,编译器不会创建T [],而是创建Object []。这是编译器生成的内容:

示例(与上述相同,通过类型擦除进行翻译后):

public final class Test {  
        static Object[] method_1( Object t1, Object t2) { 
            return method_2( new Object[] {t1, t2} );                   // unchecked warning 
        } 
        static Object[] method_2( Object[] args) { 
            return args; 
        } 
        public static void main(String[] args) { 
            String[] strings = (String[]) method_1("bad", "karma");       // ClassCastException 
        } 
}
  

发出未经检查的警告是为了提醒您潜在的风险   类型安全违规和意外的ClassCastExceptions

在示例中,您将在ClassCastException方法中观察到main(),其中两个字符串都传递给第一个方法。在运行时,这两个字符串填充到Object[]中;注意not a String[]

第二种方法接受Object[] as an argument,因为类型擦除之后的Object[]是其声明的参数类型。因此,第二种方法返回Object[] , not a String[],它作为第一种方法的返回值传递。最终,main()方法中由编译器生成的强制转换失败,because the return value of the first method is an Object[] and no String[]

  

结论

最好避免在期望可变参数列表的地方提供非可验证类型的对象。您将始终收到未经检查的警告,并且除非您确切知道所调用的方法是什么,否则您将永远无法确保调用是类型安全的。

答案 2 :(得分:5)

由于在编译期间清除了通用类型,因此必须使用T的Class实例进行强制转换

public class Test {

    public static void m1() {
        m3(m4("1", String.class));
    }

    public static void m2() {
        m3(m4("1", String.class), m4("2", String.class));
    }

    public static void m3(final Object... str) {
        for (Object o : str) {
            System.out.println(o);
        }
    }

    public static <T> T m4(final Object s, Class<T> clazz) {
        return clazz.cast(s);
    }

    public static void main(String[] args) {
        m1();
        m2();
    }
}
$java Test
1
1
2

答案 3 :(得分:3)

在Java中,Varargs和Generics混合得不好。这是因为

  • 通过在运行时具有相应类型的数组(在您的情况下为Object数组)实现的变量
  • 数组和泛型不兼容。您不能有一个字符串列表数组。