【码农每日一题】Java 泛型实例化、数组踩坑面试题

Java中不能直接用具体泛型类型初始化泛型数组,以防运行时的ClassCastException。通配符形式的初始化是允许的,但需要类型转换。官方文档对此有详细解释。正确初始化泛型数组的方法是使用反射,或者避免使用泛型数组,转而使用列表集合。Java编译期无法确定泛型参数化类型,故不能直接用`new T()`实例化泛型对象,但可通过反射实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问:为什么 Java 的泛型数组不能采用具体的泛型类型进行初始化?

答:这个问题可以通过一个例子来说明。

 
List<String>[] lsa = new List<String>[10]; // Not really allowed.    

Object o = lsa;    

Object[] oa = (Object[]) o;    

List<Integer> li = new ArrayList<Integer>();    

li.add(new Integer(3));    

oa[1] = li; // Unsound, but passes run time store check    

String s = lsa[1].get(0); // Run-time error: ClassCastException.

由于 JVM 泛型的擦除机制,所以上面代码可以给 oa[1] 赋值为 ArrayList 也不会出现异常,但是在取出数据的时候却要做一次类型转换,所以就会出现 ClassCastException,如果可以进行泛型数组的声明则上面说的这种情况在编译期不会出现任何警告和错误,只有在运行时才会出错,但是泛型的出现就是为了消灭 ClassCastException,所以如果 Java 支持泛型数组初始化操作就是搬起石头砸自己的脚。而对于下面的代码来说是成立的:

List<?>[] lsa = new List<?>[10]; // OK, array of unbounded wildcard type.    

Object o = lsa;    

Object[] oa = (Object[]) o;    

List<Integer> li = new ArrayList<Integer>();    

li.add(new Integer(3));    

oa[1] = li; // Correct.    

Integer i = (Integer) lsa[1].get(0); // OK

所以说采用通配符的方式初始化泛型数组是允许的,因为对于通配符的方式最后取出数据是要做显式类型转换的,符合预期逻辑。综述就是说 Java 的泛型数组初始化时数组类型不能是具体的泛型类型,只能是通配符的形式,因为具体类型会导致可存入任意类型对象,在取出时会发生类型转换异常,会与泛型的设计思想冲突,而通配符形式本来就需要自己强转,符合预期。

关于这道题的答案其 Oracle 官方文档给出了原因:https://docs.oracle.com/javase/tutorial/extra/generics/fineprint.html

问:下面语句哪些是有问题,哪些没有问题?

 
List<String>[] list1 = new ArrayList<String>[10];    //编译错误,非法创建

List<String>[] list2 = new ArrayList<?>[10];    //编译错误,需要强转类型

List<String>[] list3 = (List<String>[]) new ArrayList<?>[10];    //OK,但是会有警告

List<?>[] list4 = new ArrayList<String>[10];    //编译错误,非法创建

List<?>[] list5 = new ArrayList<?>[10];    //OK

List<String>[] list6 = new ArrayList[10];    //OK,但是会有警告



答:上面每个语句的问题注释部分已经阐明了,因为在 Java 中是不能创建一个确切的泛型类型的数组的,除非是采用通配符的方式且要做显式类型转换才可以。

 

问:如何正确的初始化泛型数组实例?

答:这个无论我们通过 new ArrayList[10] 的形式还是通过泛型通配符的形式初始化泛型数组实例都是存在警告的,也就是说仅仅语法合格,运行时潜在的风险需要我们自己来承担,因此那些方式初始化泛型数组都不是最优雅的方式,我们在使用到泛型数组的场景下应该尽量使用列表集合替换,此外也可以通过使用 java.lang.reflect.Array.newInstance(Class<T> componentType, int length) 方法来创建一个具有指定类型和维度的数组,如下:

所以使用反射来初始化泛型数组算是优雅实现,因为泛型类型 T 在运行时才能被确定下来,我们能创建泛型数组也必然是在 Java 运行时想办法,而运行时能起作用的技术最好的就是反射了。

问:Java 泛型对象能实例化 T t = new T() 吗,为什么?

答:不能,因为在 Java 编译期没法确定泛型参数化类型,也就找不到对应的类字节码文件,所以自然就不行了,此外由于 T 被擦除为 Object,如果可以 new T() 则就变成了 new Object(),失去了本意。如果要实例化一个泛型 T 则可以通过反射实现(实例化泛型数组也类似),如下:

static <T> T newTclass(Class<T> clazz) throws

                       InstantiationException,

                       IllegalAccessException {  

   T obj = clazz.newInstance();  

   return obj;  

}

原因就不解释了,姑且可以认为和上面泛型数组创建一个原因,至于本质深层次原因请关注后边关于泛型反射面试题的推送。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值