Java - 泛型数组

2017-11-25  本文已影响0人  xiaofudeng

Java不允许创建泛型数组的原因

上面链接中的Xuan Luo的回答很好. 简单来说就是Java数组必须有一个具体的类型信息, 但是因为泛型擦除, 所以无法提供具体的类型信息给数组, 因而无法创建泛型数组. 而容器其实泛型擦除后根本没类型信息了, 所以全部的List<AnyType>最后都是List. 看下面这个例子:

    public static void testTypeInformation(){
        String[] strings1 = new String[0];
        String[] strings2 = new String[0];
        Integer[] integers = new Integer[0];
        System.out.println(String.format("strings1.class == strings2.class: %s", strings1.getClass() == strings2.getClass()));
        // 直接这样写编译器直接给出了错误
        // strings1.getClass() == integers.getClass()
        // System.out.println(String.format("strings1.class == integers.class: %s", strings1.getClass() == integers.getClass()));
        Class<?> stringArrayClass = strings1.getClass();
        Class<?> integerArrayClass = integers.getClass();
        System.out.println(String.format("strings1.class == integers.class: %s", stringArrayClass == integerArrayClass));

        // 再看容器类
        List<String> stringList = new ArrayList<>();
        List<Integer> integerList = new ArrayList<>();
        System.out.println(String.format("stringList.class == integerList.class: %s", stringList.getClass() == integerList.getClass()));

    }

输出:

strings1.class == strings2.class: true
strings1.class == integers.class: false
stringList.class == integerList.class: true

可以看到数组是确确实实有具体类型, 而泛型容器最终都是容器class本身, 没有新的类生成.

一些创建泛型数组的方法

调用反射包的方法

    /**
     * 该方法可以安全地创建泛型数组
     * 调用反射包里的Array.newInstance(type, length)方法.
     * 具体的实现是靠native方法.
     * @param type 元素类型
     * @param length 长度
     * @param <T> 类型
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> T[] createArray(Class<T> type, int length){
        return (T[]) Array.newInstance(type, length);
    }

强制类型转换 (错误方法)

    /**
     * 编译不会报错, 但是运行时
     * 只要T不是Object, 那一定会报错
     * 因为Java对于数组的定义, 是必须要有具体类型的
     * 虽然 String 可以转换为 Object, 但是Object不一定能转换成String
     * 一样的道理 String[] 可以转换为 Object[], 但是Object[]不一定能
     * 转换成String[], 另外String[]转换为Object[]是合法的, 但是千万
     * 不要这样做, 很可能会出错, 因为转换为Object[]就能存任意引用了,
     * 但是底层仍然是String[], 如果存的不是String, 那么就会报错.
     * @param len 长度
     * @param <T> 类型参数, 可由类型推导推导得知, 也可以显式
     *           指定, ClassName.<Type>function()
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> T[] createArray(int len){
        return (T[]) new Object[len];
    }

测试:

        // ClassCastException
        // Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
        // 使用了类型推导
        try {
            Integer[] integers = createArray(10);
        } catch (ClassCastException e){
            System.out.println("exception: " + e.getMessage());
        }

        // 也可以这样, 显式指定类型
        // Integer[] integerArray = GenericArray.<Integer>createArray(10);

因为数组的实际类型是Object[], 是无法转型为Integer[]的.

限制访问的Object[]方法

    /**
     * 使用底层Object[]来存储元素的折中做法
     * 不能暴露Object[]出去, 否则无法保证
     * 往里面加的类型是否安全
     * @param <T>
     */
    public static class MyArray<T>{

        // 存放实际的数据
        // 因为所有对象都能向上转型为Object
        // 所有用Object[]来存各类对象是没问题的
        private Object[] innerArray;

        public MyArray(int len){
            innerArray = new Object[len];
        }

        @SuppressWarnings("unchecked")
        public T get(int index){
            // 只要set方法中传入的确实是T类型的对象
            // 那么这里一定是安全的
            return (T) innerArray[index];
        }

        public void set(int index, T item){
            innerArray[index] = item;
        }

        @Override
        public String toString() {
            return Arrays.toString(innerArray);
        }
    }

测试:

        MyArray<String> stringArray = new MyArray<>(3);
        stringArray.set(0, "The");
        stringArray.set(1, "safe");
        stringArray.set(2, "generic array");
        System.out.println(stringArray);

输出:

[The, safe, generic array]
上一篇下一篇

猜你喜欢

热点阅读