我正在阅读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...)
。
可变参数方法的调用者创建数组。换句话说,这个:
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] 与未具体化的泛型相反,因此,您不能询问列表,例如其组件类型。