列表优先于数组

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

  //编译通过,但是会抛出异常
        Object[] array = new Long[2];
        array[0] = " dd";

        //编译不通过
        ArrayList num = new ArrayList();
        num.add("dd");

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

new list[]
new list[]
new E[]

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

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

当你得到泛型数组创建错误时候,最好的办法就是优先使用集合类型List,而不是数组类型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 apply(T arg1,T arg2);
    }

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

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

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

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

你可能感兴趣的:(列表优先于数组)