Java 泛型实例化、数组

问:为什么 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>[] list11 = new ArrayList<String>[10]; //编译错误,非法创建 
        List<String>[] list12 = new ArrayList<?>[10]; //编译错误,需要强转类型 
        List<String>[] list13 = (List<String>[]) new ArrayList<?>[10]; //OK,但是会有警告 
        List<?>[] list14 = new ArrayList<String>[10]; //编译错误,非法创建 
        List<?>[] list15 = 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) 方法来创建一个具有指定类型和维度的数组,如下:

        public class ArrayWithTypeToken<T> {
            private T[] array;

            public ArrayWithTypeToken(Class<T> type, int size) {
                array = (T[]) Array.newInstance(type, size);
            }

            public void put(int index, T item) {
                array[index] = item;
            }

            public T get(int index) {
                return array[index];
            }

            public T[] create() {
                return array;
            }
        }
        ArrayWithTypeToken<Integer> arrayToken = new ArrayWithTypeToken<Integer>(Integer.class, 100);
        Integer[] array = arrayToken.create();

所以使用反射来初始化泛型数组算是优雅实现,因为泛型类型 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;
        }

可以认为和上面泛型数组创建是一个原因。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 前言 人生苦多,快来 Kotlin ,快速学习Kotlin! 什么是Kotlin? Kotlin 是种静态类型编程...
    任半生嚣狂阅读 26,276评论 9 118
  • object 变量可指向任何类的实例,这让你能够创建可对任何数据类型进程处理的类。然而,这种方法存在几个严重的问题...
    CarlDonitz阅读 934评论 0 5
  • 一把钥匙开一把锁是生活中的一种常识,连三岁大的小孩子都懂得。而且,一把钥匙开一把锁同时还是一种可以推广的理论,也就...
    vardump阅读 163评论 0 0
  • "我希望有个如你一般的人,如这山间清晨一般明亮清爽的人,如奔赴古城道路上阳光一般的人,温暖而不炙热,覆盖我所有肌肤...
    psyche将会变身阅读 323评论 0 2