使用 Java-8 时可变参数中的 ClassCastException

Jok*_*ker 5 java generics exception java-8

以下代码工作正常,m2()ClassCastException在我使用m1().

m1和之间的唯一区别m2是参数的数量。

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();
   }
 }
Run Code Online (Sandbox Code Playgroud)

我的问题是 - 当我们使用泛型时,varargs 是否不适用于单个参数?

PS:这与使用泛型和可变参数的 ClassCastException无关

ern*_*t_k 10

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

在此声明中:

Test.m3(Test.m4("1"));
Run Code Online (Sandbox Code Playgroud)

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

Test.m4("1"); // T is Object
Run Code Online (Sandbox Code Playgroud)

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

Test.<String>m4("1"); // T is String
Run Code Online (Sandbox Code Playgroud)

...或通过在赋值上下文中使用表达式:

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

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

Integer.parseInt(Test.m4("1")); // T is String
Long.toString(Test.m4("1")); // T is Long
Run Code Online (Sandbox Code Playgroud)

现在,回到Test.m3(Test.m4("1"));:我找不到对此的引用,但我相信编译器被迫T解析 的参数类型m3,即Object[]. 这意味着T,它必须与参数类型一致m3,因此,决心Object[],并就好像您指定的通用类型,这使得它:

Test.m3(Test.<Object[]>m4("1")); // this is what is happening
Run Code Online (Sandbox Code Playgroud)

现在,因为m4不是返回 an Object[]m3而是接收 a String,这导致了不可避免的ClassCastException.

如何解决?

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

Test.m3(Test.<String>m4("1")); 
Run Code Online (Sandbox Code Playgroud)

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

String temp = m4("1");
m3(temp);
Run Code Online (Sandbox Code Playgroud)

@Ravindra Ranwala 删除的答案中建议了第二种方法。在我看来,这归结为注意编译器警告:

public static <T> T m4(Object s) {
    return (T) s; // unchecked cast
} 
Run Code Online (Sandbox Code Playgroud)

未检查铸警告只是告诉你,编译器(和运行时)不打算强制类型兼容性,仅仅是因为T不知道,你投。以下版本是类型安全的,但它也使编译器String用作 的返回类型m4以及参数的类型m3

public static <T> T m4(T s) {
    return s;
}
Run Code Online (Sandbox Code Playgroud)

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

  • 声明“public static &lt;T&gt; T m4(Object s)”基本上表示“它将返回调用者想要的任何内容”,这当然是错误的,除非该方法总是返回“null”、抛出异常或从不返回。因此,唯一明智的解决方法是更改​​此方法。另请参见 /sf/ask/2731535411/ 是的,根据经验,编译器仅在没有其他调用适用时才会使用 varargs 调用。因此,当它可以通过推断数组类型来调用该方法时,就可以了。 (9认同)