美文网首页
列表优先于数组

列表优先于数组

作者: 没走过的二丁目 | 来源:发表于2018-07-26 15:55 被阅读0次

    数组于泛型相比较,有两个重要的不同点。首先,数组是协变的(covariant)。这个词听起来有点吓人,其实是表示如果Sub为Super的子类型,那么数字sub[]类型,就是super[]类型的子类型。相反泛型是不可变的(invariant):对于任意两个不同的类型type1和type2,list<type1>和既不是list<type2>的子类型,也不是他的超类型,你可能认为泛型是有有缺陷的,但是实际上数组才是有缺陷的 ,举个例子

      //编译通过,但是会抛出异常
            Object[] array = new Long[2];
            array[0] = " dd";
    
            //编译不通过
            ArrayList<Long> num = new ArrayList<Long>();
            num.add("dd");
    

    上面两种方法无论哪个都无法将String放进long容器,但使用数组你在运行时候才能发现错误,而使用列表在编译的时候就知道。
        数组于泛型之间的第二大区别在于,数组是具体化的(reified)因此数组会在运行时才知道并检查他们的元素类型约束。如上所示将String放到long中会得到一个ArraystoreException异常,相比之下,泛型则是通过擦除来实现的,因此泛型只在编译的时候强化类型信息,在运行的时候擦除元素类型信息
        由于上述这些根本的区别,因此数组和泛型不能很好的混合使用。例如,创建泛型,参数化类型或者类型参数的数组是非法的

    new list<String>[]
    new list<E>[]
    new E[]
    

    这些都会在编译的时候报错:generic array creation(泛型数组创建错误)
    那么为什么创建泛型数组是非法的?
    因为它不是类型安全的,要是他合法,编译器在其他正确的程序中发生的转换就会在运行时候失败,并抛出一个ClassCastExeception异常,这就违背了泛型系统提供的基本特征。 举个栗子

            List<String>[] stringLists=new List<String>[1];//创建一个泛型数组,假设合法
            List<Integer> intList= Arrays.asList(42);//创建并初始化一个包含单个元素的List<Integer>
            Object[] objects=stringLists;//将List<String>数组保存到一个Object数组变量中,这是合法的,因为数组和协变的。
            objects[0]=intList;//将List<Integer>保存到Object数组里唯一的元素中,这是可以的,因为泛型是通过擦除实现的。
            String s=stringLists[0].get(0);//我们从这个数组里唯一的列表中获取唯一的元素,编译器会自动地获取到元素转换成String,但它是一个Integer,因此,我们在运行时得到一个ClassCastException。
            //为了防止这种情况(创建泛型数组),第一行就产生了一个编译时错误。
    
    

    当你得到泛型数组创建错误时候,最好的办法就是优先使用集合类型List<E>,而不是数组类型E[],举个复杂栗子
    假设有一个(collections.synchronizedList返回的那种)同步列表和一个函数,假设要编写一个方法reduce,并使用apply函数来处理这个列表,列表元素类型为整数,并且函数是用来做两个整数的求和运算,reduce就会返回列表中所有元素的总和,如果函数是返回两个数的乘积,那么reduce就返回整个列表的乘积,代码如下

     static Object reduce(List list,Function f,Object initVal){
            synchronized (list){
                Object result = initVal;
                for(Object o:list){
                    result = f.apply(result,o);
                }
                return result;
            }
    
        }
        interface Function{
            Object apply(Object arg1,Object arg2);
        }
    

    第67条中告诉我们:不要从同步区域中调用外来方法,因此在持有锁的时候修改reduce方法来复制列表中的内容,要这么做一般用List的toArray方法(它在内部锁定列表)

     static Object reduce(List list,Function f,Object initVal){
            Object [] snaphot = list.toArray();//lock list
                Object result = initVal;
                for(Object o:snaphot){
                    result = f.apply(result,o);
                }
                return result;
    
        }
    

    如果视试图用泛型来完成这一点,就会遇到之前讨论的麻烦,以下是Function的泛型版

     interface Function<T>{
            T apply(T arg1,T arg2);
        }
    

    那么我们来天真的修改reduce方法

    static <E> E reduce(List<E> list,Function<E> f,E initVal){
            E [] snaphot = list.toArray();//error required E[]  found Object[]
                E result = initVal;
                for(E o:snaphot){
                    result = f.apply(result,o);
                }
                return result;
        }
    

    由于上述编译错误,你可能会说,没什么大不了的,将类型强转就行

     E [] snaphot = (E[]) list.toArray();
    

    这样错误是消除了 但是却出现了一条警告


    image.png

    编译器告诉我们,它无法在运行时候检查转换的安全性,因为它在运行时候还不知道E是什么,这段程序虽然是可以运行的但是,通过微小的改动,就可以让它在没有包含显示转换的行上抛出ClassCastExeception

    那么应该怎么办??? 就是用列表代替数组 代码如下:

    static <E> E reduce(List<E> list, Function<E> f, E initVal) {
            List<E> snaphot;
            synchronized (list) {
                snaphot = new ArrayList<>(list);
            }
            E result = initVal;
            for (E o : snaphot) {
                result = f.apply(result, o);
            }
            return result;
        }
    

    这个版本的代码虽然冗长,但是可以确定在运行时候不会得到任何的转换异常。

    数组和泛型有着非常不同的类型规则。数组是协变且可以具体化的;泛型是不可变的且可以被擦除的。因此,数组提供了运行时的类型安全,但是没有编译时的类型安全,反之,对于泛型也一样。一般来说,数组和泛型不能很好地混合使用。如果你发现自己将它们混合起来使用,并且得到了编译时错误或者警告,你的第一反应就应该是用列表代替数组。

    相关文章

      网友评论

          本文标题:列表优先于数组

          本文链接:https://www.haomeiwen.com/subject/gnlpmftx.html