3 回答
TA贡献1951条经验 获得超3个赞
第一码
List<Integer>[] array = (List<Integer>[]) new Object[size];
第一个代码失败的原因是因为转换并没有改变数组的实际类型,它只是让编译器接受代码是有效的。想象一下,如果您有另一个对底层对象数组的引用:
final int size = 2;
Object[] objectArr = new Object[size];
List<Integer>[] integerArr = (List<Integer>[]) objectArr; // Does not work
objectArr[0] = "foobar";
List<Integer> i = integerArr[0]; // What would happen ??
上面的代码编译得很好,因为你强制编译器接受它的强制转换。但是您已经可以看到为什么强制转换在运行时工作会是一个问题:您最终会得到一个List<Integer>[]
现在包含一个 的String
,这是没有意义的。所以语言不允许这样做。
第二码
E[] array = (E[]) new Object[size];
Java 中的泛型有点奇怪。由于各种原因,例如向后兼容性,泛型基本上被编译器擦除并且(大部分)不会出现在编译代码中(类型擦除)。相反,它将使用一系列规则(JLS 规范)来确定代码中应该使用哪种类型。对于基本的 unbouded 泛型;这种类型将是Object
。因此,假设没有绑定E
,编译器将第二个代码更改为:
Object[] array = (Object[]) new Object[size];
因此,由于两个数组在擦除后具有完全相同的类型,因此在运行时没有问题,并且转换基本上是多余的。
值得注意的是,这仅在E
不受限制的情况下才有效。例如,这将在运行时失败并显示ClassCastException
:
public static <E extends Number> void genericMethod() {
final int size = 5;
E[] e = (E[]) new Object[size];
}
那是因为Ewill be erased to Number,你会遇到和第一个代码一样的问题:
Number[] e = (Number[]) new Object[size];
在使用代码时记住擦除很重要。否则,您可能会遇到代码行为与您预期不同的情况。例如,下面的代码编译和运行没有异常:
public static <E> void genericMethod(E e) {
final int size = 2;
Object[] objectArr = new Object[size];
objectArr[0] = "foobar";
@SuppressWarnings("unchecked")
E[] integerArr = (E[]) objectArr;
integerArr[1] = e;
System.out.println(Arrays.toString(integerArr));
System.out.println(e.getClass().getName());
System.out.println(integerArr.getClass().getName());
}
public static void main(String[] args) {
genericMethod(new Integer(5)); // E is Integer in this case
}
第三码
List<Integer>[] array = (List<Integer>[]) new ArrayList[size];
与上面的情况类似,第三个代码将被擦除为以下内容:
List[] array = (List[]) new ArrayList[size];
这没问题,因为ArrayList
是List
.
第四码
List<Integer>[] array = new ArrayList<Integer>[size];
以上不会编译。规范明确禁止使用具有泛型类型参数的类型创建数组:
如果正在初始化的数组的组件类型不可具体化(第 4.7 节),则会出现编译时错误。
具有不是无限通配符 ( ?
) 的泛型参数的类型不满足可具体化的任何条件:
当且仅当满足以下条件之一时,类型才可具体化:
它指的是非泛型类或接口类型声明。
它是一种参数化类型,其中所有类型参数都是无限通配符(第 4.5.1 节)。
它是原始类型 (§4.8)。
它是原始类型(§4.2)。
它是一种数组类型 (§10.1),其元素类型是可具体化的。
它是一个嵌套类型,其中对于由“.”分隔的每个类型 T,T 本身是可具体化的。
TA贡献1810条经验 获得超4个赞
虽然我没有时间深入挖掘JLS
,但我可以暗示您要看得更远(尽管每次我这样做,都不是一次愉快的旅行)。
List<Integer>[] array = (List<Integer>[]) new Object[size];
这不会编译,因为它们是可证明的不同类型(搜索JLS
这样的概念)。简而言之,编译器“能够”看到这些类型不可能与可能被强制转换的类型相同,因此失败。
另一方面:
array = (E[]) new Object[10];
这些不是可证明的不同类型;编译器无法判断这一定会失败。这里的另一件事是,编译器不会以任何形式或形状强制转换为泛型类型,您可以轻松地做这样的事情(仍然可以编译):
String s[][][] = new String[1][2][3]; array = (E[]) s; // this will compile, but makes little sense
第二点是类型擦除(又有JLS
)。
编译代码后,E[]
在运行时,是Object[]
(除非有界限,但这里不是这种情况),很明显你可以把任何你想要的东西放进去。
TA贡献1886条经验 获得超2个赞
Java 中数组和泛型之间的交互是混乱的,因为它们建立在不同的假设之上。Java 数组有运行时类型检查,泛型只有编译时类型检查。
Java 通过结合编译时和运行时检查来实现类型安全。转换绕过了大部分编译时检查,但仍然有运行时检查。数组与其包含的元素类型具有本质上相同的类型兼容性规则。所以:
Object[] a = new String[size]; //ok, but be aware of the potential for an ArrayStoreException
String[] a = new Object[size]; //compile error
String[] a = (String[]) new Object[size]; //runtime error
当 Sun 决定将泛型添加到 Java 时,他们决定使用泛型的代码应该在现有的 JVM 上运行,因此他们决定通过擦除来实现泛型。泛型类型只存在于编译时,在运行时它们被普通类型取代。
所以在擦除之前我们有以下语句。
List<Integer>[] array = (List<Integer>[]) new Object[size];
E[] array = (E[]) new Object[size];
List<Integer>[] array = (List<Integer>[]) new List[size];
擦除后我们有。
List[] array = (List[]) new Object[size]; //run time error.
Object[] array = (Object[]) new Object[size]; //no error.
List[] array = (List[]) new List[size]; //no error.
E[] array = (E[]) new Object[size];应谨慎使用该结构,它违反了 Java 的正常类型模型,如果数组返回到非泛型上下文,将导致令人困惑的 ClassCastException 。不幸的是,通常没有更好的选择,因为类型擦除,泛型类型无法找出它的元素类型并构造正确类型的数组。
添加回答
举报