【Effective Java】条25:列表优先于数组

数组和列表有两个很大的不同:
1. 数组是协变,列表是不变的。意思是当类A是类B的子类时,则A[]B[]的子类;而对于列表,对于任何两个不同的类型Type1Type2,都不会存在List<Type1>List<Type2>的子类或者父类。

  1. 数组类型是具体的,即数组明确知道元素的类型并强制在运行时确定元素的类型;列表是擦除的,所以在编译的时候就会明确要求类型统一。如:
Object[] objectArray = new Long[1];
objectArray[0] = "I don't fit in";  //抛出ArrayStoreException

List<Object> ol = new ArrayList<Long>();  //编译不通过
ol.add("I don't fit in");

以上代码进行比较,相信大家都会选择采用列表的方式,毕竟错误是越早发现越好。

由于以上两个原因,所以数组和泛型不能很好的混合一起工作。最明显的就是,你不能创建泛型的数组,如:new List<E>[]new List<String>[]new E[]都是错误的,在IDE中都会提示Error Java:创建泛型数组

那为什么不能创建泛型数组呢?最主要原因就是类型安全问题。因为编译器在其他正确的程序中发生的转换就会在运行时失败,并抛出ClassCastException异常。如下所示:

List<String>[] stringLists = new List<String>[1];   //(1)其实编译不通过,我们假设可以运行,以此说明问题
List<Integer> intList = Arrays.asList(42);    //(2)
Object[] objects = stringLists;  //(3)
objects[0] = intList;
String s = stringLists[0].get(0);

以上代码中,代码(1)表示新建个泛型数组,代码(2)新建个列表,且有一个类型为Integer的元素42,由于数组是协变的,所以代码(3)成立,就是将objects指向stringLists数组,代码(4)将代码(2)新建的列表赋值给objects的首元素,代码(5)表示通过stringLists获取其中的首元素,并赋值给String类型。大家想想,Integer赋值给String会产生什么,ClassCastException就出来了。

由于泛型数组不能创建,导致很多时候比较麻烦,譬如泛型不能返回元素类型的数组、当你在使用多参数方法时提示你的警告等。所以当你遇到泛型数组错误的时候,最好的解决办法就是使用列表进行替换。以如下示例说明,假设有个同步列表listCollections.synchronizedList返回的)和一个函数apply,函数的作用是每次取列表两个数进行对应操作并返回,如果列表的值为Integer,则将两个数相加,如果列表的值为String,则将两个值连接。现在需要写个方法reduce来将列表list应用到方法apply上,reduce方法除了list和函数apply之外,还提供个initVal,表示默认值,如果元素为String,则为"",如果为Integer,则为0。代码如下所示:

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);
}

以上代码中,由于list是同步列表synchronizedList,所以我们可以直接采用同步列表的内置锁。从另一方面来看,上面的代码同步块中包含了其余的代码,其实仅仅在遍历的时候需要,调用apply的时候都不需要的,导致锁范围太大。优化后:

static Object reduce(List list, Function f, Object initVal) {
    Object[] snapShot = list.toArray();

    Object result = initVal;
    for (Object o : snapShot) {
        result = f.apply(result, o);
    }

    return result;
}

考虑到列表元素可能是Integer或者String等类型,且reduce方法尽量不要使用原型类型。如果改为了泛型:

static <E> E reduce(List<E> list, Function<E> f, E initVal) {
    E[] snapShot = (E[])list.toArray();

    E result = initVal;
    for (E o : snapShot) {
        result = f.apply(result, o);
    }

    return result;
}

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

此时,代码经过检验过后,会给出警告warning: [unchecked] unchecked cast found :Object[], required: E[] E[] snapshot = (E[]) list.toArray();。这个时候由于程序是没有问题的,只能在E[] snapShot = (E[])list.toArray()上注解SupressWarning

其实如果至始至终只按照列表的操作,可以按如下所示:

static <E> E reduce(List<E> list, Function<E> f, E initVal) {
    List<E> snapList;

    synchronized(list) {
        snapList = new ArrayList<E>(list);
    }

    E result = initVal;
    for (E o : snapList) {
        result = f.apply(result, o);
    }

    return result;
}

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

综上所述,数组和泛型拥有不同的类型规则。数组是协变并具象的,泛型是不变和擦除性质的。导致的结果就是数组在运行时提供安全性检查但是编译时并不能,而列表正相反。通常来说,数组和泛型并不能很好的混合使用,如果你发现你已经混合使用了,且在代码检查过程中报出了警告,则最好采用列表代替数组。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值