仅在第二级之后返回 Varargs 数组才会导致异常

问题描述 投票:0回答:1

我正在阅读Effective Java。作者在那里提到返回 varargs 数组可能会导致堆污染并可能导致 ClassCastException。为了支持这一点,他给出了一个示例,我在下面添加了修改后的版本。

public class Main {

    private static class A<T>{

    }

    static <T> T[] fun(T... arr){
        return arr;
    }

    static <T> T[] fun2(T a, T b){
        return fun(a, b);
    }

    public static void main(String[] args) {
        String[] z = fun("1", "2"); // no error
        String[] s = fun2("1","2"); // ClassCastExp
    }
}

String[] s = fun2("1","2");
上获得 ClassCastException 的解释是,由于类型擦除,
fun2(T, T)
将返回
Object[]
,它不会转换为
String[]
,因为 Object 不是 String 的子类型。

我的问题是为什么我在线上没有遇到异常

String[] z = fun("1", "2");
。因为
fun(T...)
还返回通用数组,该数组将在运行时转换为 Object[] 数组,因此应抛出 ClassCastExcptn ,原因与上述相同。
fun2(T, T)
在内部使用
fun(T...)

java generics compiler-errors arguments effective-java
1个回答
0
投票

可变参数方法的调用者创建数组。换句话说,这个:

static <T> T[] fun(T... arr) {
  return arr;
}

Object x = fun("a", "b");

是语法糖1

static Object[] fun(Object[] in) {
  return in;
}

Object x = fun(new String[] {"a", "b"});

关键要点是调用者执行

new String[2]
部分。

因为是调用者在执行此操作,所以调用

fun("a", "b")
会生成一个字符串数组。您可以验证这一点;数组已具体化,因此您可以询问数组其组件类型2.:

static <T> T[] fun(T... args) {
  return args;
}

Object x = fun("a", "b");
System.out.println(x.getClass().getComponentType());
// The above prints 'java.lang.String'.

同样的原则也适用于

fun2
的情况。不过,仔细一看!生成数组的代码是使用可变参数调用方法的代码(因此,fun
- 在第二种情况下,是
fun2
中的代码执行此操作,而不是
您的主要方法。 并且,泛型是编译器想象的虚构内容。无论您在编译时(即您编写时)收集到的有关类型的任何信息,都会发生。

fun2

的定义绝对不知道 String 以任何方式参与其中。它只是 T,没有界限,所以

Object
就是它所知道的一切,因此,
new Object[2]
就是它所创造的。 t 尽职尽责地将其传递给
fun
,后者将该数组传递回
fun2
,而
fun2
现在将其返回到尝试将此数组分配给类型为
String[]
aaaa 和
ClassCastException
类型的变量的代码,因为你可以'不要那样做。
我们也可以在没有 ClassCastException 的情况下检查这一点:
static <T> T[] fun(T... args) {
  return args;
}

static <T> T[] fun(T a, T b) {
  return fun(a, b);
}

Object x = fun("a", "b");
System.out.println(x.getClass().getComponentType());
// The above prints 'java.lang.Object'.

Object x =
 替换 
String[] x =

部分,编译器将允许它,但通过向

String[]
添加不可见的强制转换来允许它。在运行时,该转换将失败,因此出现 ClassCastException。

[1]“语法糖”是一个有点模糊的术语。一般来说,它意味着: 是编写构造的不同方式,并且该构造的实现就像编译器只是将其重写为另一个构造一样。换句话说,它就像一个宏。这意味着:两个片段都生成完全相同的类文件(除了行号表等不相关的内容)。

[2] 与未具体化的泛型相反,因此,您不能询问列表,例如其组件类型。

© www.soinside.com 2019 - 2024. All rights reserved.