偶遇泛型数组

在做20/11/11的LeetCode每日一题,创建了一个泛型数组,定义语句为:List<Integer>[] position = new List[26]。在语句中,只有引用有泛型,对象没有泛型(添加泛型会报错),会有一个Unchecked cast的警告。于是查阅了《Java编程思想》和网上的博客,大概弄清楚了原因,整理如下。

通常,数组与泛型不能很好地结合。你不能实例化具有参数化类型的数组:

Peel<Banana> peels = new Peel<Banana>[10]; // Illegal

擦除会移除参数类型信息,而数组必须知道它们所持有的确切类型,以强制保证类型安全。

泛型与擦除

Java泛型是使用擦除来实现的,这意味着当你在使用泛型时,任何具体的类型信息都被擦除了,你唯一知道的就是你在使用一个对象。泛型类型只有在静态类型检查期间才出现,在此之后,程序中所有泛型类型都将被擦除,替换为它们的非泛型上界。例如,诸如List<T>这样的类型注解将被擦除为List,而普通的类型变量在未指定边界的情况下将被擦除为Object

数组与确切类型

Java SE Specifications中明确规定:数组内的元素必须是 “物化” 的。

It is a compile-time error if the component type of the array being initialized is not reifiable (§4.7).

对于 “物化” 的第一条定义就是不能是泛型:

A type is reifiable if and only if one of the following holds:

  • It refers to a non-generic class or interface type declaration.
  • … …

因为array的具体实现是在虚拟机层面,这里使用javap反编译查看具体初始化数组的字节码。反编译如下代码

String[] s = new String[] {"hello"};
int[] i = new int[] {1, 2, 3};

反编译字节码如下:

 0: iconst_1
 1: anewarray     #2                  // class java/lang/String
 4: dup
 5: iconst_0
 6: ldc           #3                  // String hello
 8: aastore
 9: astore_1
10: iconst_3
11: newarray       int
13: dup
14: iconst_0
15: iconst_1
16: iastore
17: dup
18: iconst_1
19: iconst_2
20: iastore
21: dup
22: iconst_2
23: iconst_3
24: iastore
25: astore_2
26: return

其中:

  • anewarray #2:创建String数组
  • newarray int:创建int数组

查看Java Virtual Machineanewarray命令的描述

The count must be of type int. It is popped off the operand stack. The count represents the number of components of the array to be created. The unsigned indexbyte1 and indexbyte2 are used to construct an index into the run-time constant pool of the current class (§2.6), where the value of the index is (indexbyte1 << 8) | indexbyte2. The run-time constant pool item at that index must be a symbolic reference to a class, array, or interface type. The named class, array, or interface type is resolved (§5.4.3.1). A new array with components of that type, of length count, is allocated from the garbage-collected heap, and a reference arrayref to this new array object is pushed onto the operand stack. All components of the new array are initialized to null, the default value for reference types (§2.4).

anewarray字节码命令的格式是:anewarray + 具体元素类型。无法确定确切类型,就无法创建数组。

类型安全语言java

Java是“类型安全语言”。为了确保类型的安全,Java数组就非常的规矩:必须明确知道内部元素类型,而且会“记住”这个类型,每次往数组里插入新元素都会进行类型检查,不匹配会抛出 java.lang.ArrayStoreException异常。

比如下段代码,虽然声明的是Object[],但首次实例化成Integer[]。当添加String对象时,数组会进行类型检查,发现不匹配抛出异常。

Object[] nums = new Integer[10];
nums[0] = "Hello";

但是,java中的泛型使用擦除来实现的,运行时类型参数会被擦除。例如,无论你声明的是List<String>,还是List<Integer>或者原生类List,容器实际类型都是List<Object>。数组无法对泛型的类型进行检查,所以java语言中不允许实例化具有参数化类型的数组。

解决方法

一、承担风险,绕过限制

诚然,编译器确实不让你实例化泛型数组,但是,它允许你创建这种数组的引用。例如,

List<String>[] ls;

这条语句可以顺利地通过编译器而不报任何错误。而且,尽管你不能创建实际的持有泛型的数组对象,但是你可以创建非泛型的数组,然后将其转型:

List<String>[] lists = (List<String>[]) new List[10];
Object[] objs = lists;
// lists[0] = new ArrayList<Integer>();    // error
objs[1] = new ArrayList<Integer>();

这种实现方式,提示Unchecked cast: 'java.util.List[]' to 'java.util.List<java.lang.String>[]'警告,在使用List<String>[] lists引用时,能编译器能检查出泛型类型错误,而在使用Object[] objs引用无法检测出泛型类型错误问题,存在一定隐患。

如果你知道将来不会向上转型,并且需求也相对比较简单,那么你仍旧可以创建泛型数组,它可以提供基本的编译期类型检查。但是,事实上,泛型容器总是比泛型数组更好的选择。

二、使用泛型容器

一般的解决方案是在任何想要创建泛型数组的地方都使用ArrayList。比如,使用如下结构代替

List<List<String>> lists = new ArrayList<>();

参考:

  • 《Java编程思想》
  • java为什么不支持泛型数组? - 胖君的回答 - 知乎 https://www.zhihu.com/question/20928981/answer/117521433
  • Java 泛型总结(二):泛型与数组https://segmentfault.com/a/1190000005179147
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值