Effective Java笔记第四章泛型第三节列表优先于数组

本文详细比较了数组与泛型在Java中的差异,强调数组的协变性可能导致运行时错误,而泛型的不可变性提供编译时检查。通过实例和建议,提倡在代码中优先使用List而非数组以提升类型安全和代码健壮性。

Effective Java笔记第四章泛型

第三节列表优先于数组

1.数组与泛型相比,有两个重要的不同点。首先,数组是协变的(如果Sub为Super的子类型,那么数组类型Sub[ ]就是Subper[ ]的子类型)。相反,泛型是不可变的:对于任意两个不同的类型Type1和Type2,List< Type1 >既不是List< Type2 >的子类型,也不是List< Type2 >的超类型。两者相比数组是有缺陷的。我们举个例子:

public class Demo {

    public static void main(String[] args) {
        //运行报错
        Object[] objects= new Integer[1];
        objects[0]="aa";
        System.out.println(objects[0]);
        //编译报错
//        List<Object> list=new ArrayList<Long>();

    }

}

数组在运行时发现错误,但是列表在编译时发现错误,我们当然希望越早发现错误越好。

2.数组是具体化的,因此数组会在运行时才知道并检查他们的元素类型约束。泛型是通过擦除来实现的,因此泛型只有在编译时强化他们的类型信息,并在运行时丢弃(或擦除)他们的元素类型信息。(擦除就是使泛型可以与没有使用泛型的的代码随意进行互用。)

3.数组和泛型不能很好的混合使用。例如:创建泛型,参数化类型或者类型参数的数组是非法的。像new List< E >[ ],new List< String >[ ]和
new E[ ]这些都是非法的。对此我们可以举个例子:

		//1会出现编译异常
 		List<String>[] stringList = new List<String>[1];//1
        List<Integer> integerList = Arrays.asList(42);  //2
        Object[] objects1 = stringList;                 //3
        objects1[0] = integerList;                      //4
        String s = stringList[0].get(0);                //5

假设1是合法的,它创建了一个泛型数组。2创建并初始化了一个包含单个元素的List< Integer >列表。3将List< String >[ ]数组保存到了一个Object数组变量中,这是合法的,因为数组是协变的。4将List< Integer >保存到Object[ ]数组里的唯一元素中,这是可以的,因为泛型是通过擦除实现的:List< Integer >实例的运行时类型只是List。但是在5中就出现问题了,我们将一个List< Integer >实例保存到了List< String >[ ]这个只包含List< String >实例的数组中,我们从数组中获取的元素,编译器会自动把它转换为String,但他是一个Integer,因此会出现ClassCastException异常。

4.E,List< E >和List< Striing >这样的类型应称作不可具体化的类型。直观的说,不可具体化的类型是指其运行时表示法包含的信息比他的编译时表示法包含的信息更少的类型。唯一可具体化的参数化类型是无限制的通配符类型,如List<?>和Map<?,?>。虽然不常用,但是创建无限制通配类型的数组是合法的。

5.当你得到泛型数组创建错误时,最好的解决办法通常是优先使用集合类型List< E >,而不是数组类型E[ ]。这样可能会损失一些性能或者简洁性,但是换回的却是更高的类型安全性和互助性。下面我们举个例子:

public class Demo2 {

    //Reduction without generics ,and with concurrency flaw 没有泛型,并且有并发缺陷
    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 o1,Object o2);
    }

}

首先我们试一下数组类型E[ ]。

public class Demo2Improve1 {

    static <E> E reduce(List<E> list, Function<E> f, E initVal) {
        E[] snapshot = (E[]) list.toArray();
        E result = initVal;
        for (E e : snapshot) {
            result = f.apply(result, initVal);
        }
        return result;
    }

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

    public static void main(String[] args) {
        List<String> stringList = new ArrayList<String>();
        stringList.add("cc");
        stringList.add("bb");
        Function<String> function = new Function<String>() {
            @Override
            public String apply(String arg1, String arg2) {
                return arg1 + arg2;
            }
        };
        String result = reduce(stringList, function, "aa");
        System.out.println(result);
    }

}

编译器虽然不会报错,但是会警告你它无法在运行时检查转换的安全性,因为它在运行时还不知道E是什么,要牢记元素类型信息会在运行时从泛型中被擦除。虽然这段程序可运行,但是不安全。snapshot 编译时类型为E[ ],他可以是String[ ],Integer[ ]或者任何其他类型的数组。但是运行时类型为Object[ ],这是十分危险的。不可具体化的类型的数组转换只能在特殊情况下使用。

对此,我们可以用列表代替数组:

public class Demo2Improve2 {

    //List-based generic reduction
    static <E> E reduce(List<E> list, Function<E> f, E initVal) {
        List<E> snapshot;
        synchronized (list) {
            snapshot = new ArrayList<E>(list);
        }
        E result = initVal;
        for (E e : snapshot) {
            result = f.apply(result, e);
        }
        return result;
    }

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

    public static void main(String[] args) {
        List<String> stringList = new ArrayList<String>();
        stringList.add("cc");
        stringList.add("bb");
        Function<String> function = new Function<String>() {
            @Override
            public String apply(String arg1, String arg2) {
                return arg1 + arg2;
            }
        };
        String aa = reduce(stringList, function, "11");
        System.out.println(aa);
    }

}

这样的话就可以确定在运行时不会产生ClassCastException异常。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值