[使用Java-8时varargs中的ClassCastException

问题描述 投票:6回答:4

以下代码对于m2()正常工作,但是当我使用ClassCastException时抛出m1()

m1m2之间的唯一区别是参数的数量。

public class Test  {

  public static void m1() {
        m3(m4("1"));
    }

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

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

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

    public static void main(String[] args) {
        m1();
   }
 }

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

PS:与ClassCastException using Generics and Varargs

不相关
java generics exception java-8
4个回答
10
投票

让我们跳过您暂时忽略unchecked cast警告的事实,并尝试了解发生这种情况的原因。

在此声明中:

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 var-arg)调用Object...,就像您已经编写了:

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

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

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

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

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

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


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 
        } 

警告:[未检查]类型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[]

结论

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


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
投票

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

  • 通过在运行时具有相应类型的数组(在您的情况下为Object数组)实现的变量
  • 数组和泛型不兼容。您不能有一个字符串列表数组。
© www.soinside.com 2019 - 2024. All rights reserved.