1. Java中为什么不能创建泛型数组?
所谓泛型数组,是指泛型类型的数组,或直接由泛型参数定义的数组。
泛型类型的数组 ```java // 定义一个泛型类型 class Holder
{ private final T value; public Holder(T value) {
this.value = value;
} }
public class GenericArrayTest {
public static void main(String[] args) {
// 创建泛型类型的数组。无法通过编译。
Holder
// 注意:创建原始类的数组,可通过编译。
Holder<String>[] arr = new Holder[10];
}
}
- 直接用泛型参数定义数组
```java
public <T> void Create(){
// 无法通过编译
T[] arr = new T[10];
}
泛型类型擦除
泛型是Java在1.5版本才引入的。为了保证与老代码的兼容性,Java的泛型只存在于编译时。在编译后的字节码中,泛型标记都被删除,这种机制称为“类型擦除”。
对于上面的例子,假设都可以通过编译,那么编译后的代码等价于:
// 例1
Holder[] arr = new Holder[10];
// 例2。由于编译时无法得到T的类型,因此只能认为是Object。
Object[] arr=new Object[];
数组协变
Java的数组是“强类型”的,即数组本身就是有类型的,是“真泛型”(即编译后类型新依然存在,其他Java集合类是假泛型)。这意味着Java的数组中每一个元素都必须是指定的相同类型,无论编译时还是运行时。
但是,Java的数组是可协变的。这是一个设计缺陷。初衷可能是因为在泛型出现以前,想要编写处理各种类型数组的通用方法(Arrays中的方法),因此放松了类型检查。
这就导致了一个问题。以下代码是可以通过编译的:
String[] a = new String[10];
Object[] b = a;
b[0] = 1; // 可以通过编译,但运行时会报java.lang.ArrayStoreException
在原本为String[]类型的数组中,居然放入了一个int类型值,即数组元素的类型不一致,这违背了强类型要求。
但好在尽管可以通过编译,但创建数组时指定的元素类型是不会丢失的(不会擦除),Java会在运行时记住数组的元素类型,并检查放入其中的每一个元素。若类型不匹配,会报java.lang.ArrayStoreException
。因此上述代码在执行时会这个错。
如果允许创建泛型数组,会怎么样?
已知,泛型数组编译后丢失类型信息,最终生成的是原始类型的数组。即创建数组时的元素类型会丢失,在运行时无法检查放入的元素是否合法。
若允许创建泛型数组,则可能发生“堆污染”,进而造成类型转化错误ClassCastException。https://en.wikipedia.org/wiki/Heap_pollution。
比如:
Holder<String>[] strArr = new Holder<String>[10];
Object[] objArr = strArr;
objArr[0] = new Holder<String>("string");
objArr[1] = new Holder<Integer>(20);
Holder<String> readVal = strArr[1];
String s=readVal.getValue();
类型擦除后:
Holder[] strArr = new Holder[10]; // 创建的数组类型是原始类型Holder[],不包含类型参数信息
Object[] objArr = strArr;
objArr[0] = new Holder("string");
objArr[1] = new Holder(20); // 元素都是原始类型Holder。不包含类型参数信息。与数组类型匹配,不会报错。但在编译时,不同类型参数的具体Holder应属于不同类型,不能放入一个数组。这违背了强类型要求。
Holder readVal = strArr[1];
String s=readVal.getValue(); // 类型转化错误
更明显的,比如:
T[] arr = new T[];
Object[] objArr = arr;
objArr[0] = "Fly";
objArr[1] = 1;
类型擦除后:
Object[] arr = new Object[]; // 失去了数组类型信息
Object[] objArr = arr;
objArr[0] = "Fly";
objArr[1] = 1; // 无法进行运行时类型检查
总结
由于Java的泛型是假泛型,泛型数组在运行时将退化为“原始类型数组”,失去运行时类型检查。又由于数组协变的设计缺陷,失去了编译时的类型检查。从而泛型数组可能发生“堆污染”,进而可能导致类型转换错误。因此,Java不允许创建泛型数组。