复习时看到这样一个句话,“不能创造泛型的数组”,会无法通过编译,进行了简单的实验。
首先区分一下泛型数组和泛型对象的数组,前者是类似 T[ ] list=new T[3] 待创建的类型是泛型;后者是上面这种样子,创建对象是具体的,但使用了泛型标签。前者可能在泛型对象内需要使用,也不能直接创建,但是可以使用强转。在这篇文章讲解了相关内容 Java泛型总结(二)
在编译器中进行测试,发现这样写编译器会报错,提示创建泛型数组,查询资料发现,由于类型擦除,java不允许创建泛型对象的数组,第三个理由同第一个一样(上文有泛型L出现才会提示,否则会提示无法识别L);而第二个会提示无法使用<>创建。
ArrayList<String>[] list2=new ArrayList<String>[3];//编译阶段报错:创建泛型数组
ArrayList<String>[] list3=new ArrayList<>[3];//编译阶段报错:无法使用<>创建数组
ArrayList<String>[] list7=new ArrayList<L>[3];//编译阶段:创建泛型数组
ArrayList<String>[] list7=new ArrayList<?>[3];//编译阶段:类型不兼容
但是,如果抛弃后面的<String>,就可以通过编译,并且正常运行。这是因为我们抛弃了ArrayList的泛型多态,它“退化”成了原始类型,因此不再是泛型对象的数组,此时的ArrayList可以传入任何泛型类型的ArrayList,但是会提示未经检查的转换。然后由于其引用List<String>为一个泛型对象,所以只能传入泛型类型为String的ArrayList,此时的list1[0]就与普通的List无异。
List<String>[] list1=new ArrayList[3];
list1[0]=new ArrayList<>();
//list1[0]<=>List<String> list1=new ArrayList<>()
list1[0].add("ab");
System.out.println(list1[0]);
而如果我们不为List加上<String>,则其引用也是一个原始类型,会提示"形参化的List的原始使用",这时传入一个具体类型的实例也不会产生影响,与原始使用相同,可以传入任意类型的数据,并且输出[1,ab]。
List[] list1=new ArrayList[3];
list1[0]=new ArrayList<Integer>();
list1[0].add(1);
list1[0].add("ab");
System.out.println(list1[0]);
//运行结果:[1,ab]
另一种可能被想到的实现方法,进行对数组强转,就像泛型数组的强转一样,同样,创造数组时,编译器会提示未经检查的转换;但是不能将父类强转为子类,这在其他情况也是会报错的。如果不想使用危险的原始类型,就使用通配符<?>并在引用出设置类型,依然不能使用泛型L,会提示无法创建泛型数组。
//List<String>[] list3=(ArrayList<String>[])new List[3]; //强转错误
//List<String>[] list4=(ArrayList<String>[])new ArrayList<L>[3];//编译阶段错误:无法创建泛型数组
List<String>[] list5=(ArrayList<String>[])new ArrayList<?>[3];//可行
list5[0]=new ArrayList<>();
System.out.println(list5[0].size());
//运行结果:0
总结
如果一定要使用泛型对象创建数组,可以对使用泛型对象的原始类型(new后面的对象不加类型标签)或通配符(标签使用“?”)进行实例化,有以下几种做法
- 引用不设置标签,实例使用原始类型或通配符
- 引用设置标签,实例使用原始类型,限制了数组元素的泛型类型
- 引用设置标签,实例使用强转,设置为原始类型或通配符标签
关于原因,主要原理就是泛型的擦除导致不允许创建泛型数组,以及引用和实例、父类和子类的转换关系。学习过程中初步了解了原始类型的概念,以及发现同学问我的是开头这句话的上面一句话。