跟我学(Effective Java 2)第25条:列表优先于数组

本文深入探讨了数组和泛型在Java中的不同特性,包括数组的协变性和具体化,以及泛型的不变性和擦除性质。阐述了这些特性如何影响代码的安全性和效率,并提供了在实际编程中避免常见陷阱的建议。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第25条:列表优先于数组

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

//编译时 不会报错,在运行时出错ArraystoreException
Object[] o = new Long[1];
o[0] = "string";

//这段代码在编译时出错
List<Object> o = new ArrayList<Long>();
o.add("string");

数组于泛型之间的第二大区别在于,数组是具体化的(reified)因此数组会在运行时才知道并检查他们的元素类型约束。如上所示将String放到long中会得到一个ArraystoreException异常,相比之下,泛型则是通过擦除来实现的,因此泛型只在编译的时候强化类型信息,在运行的时候擦除元素类型信息。

由于以上两个原因,所以数组和泛型不能很好的混合一起工作。最明显的就是,你不能创建泛型的数组,如:new List[],new List[],new E[]都是错误的,在编译时都会提示generic array creation(创建泛型数组) 错误。

由于泛型数组不能创建,导致很多时候比较麻烦,譬如泛型不能返回元素类型的数组、当你在使用多参数方法时提示你的警告等。所以当你遇到泛型数组错误的时候,最好的解决办法就是使用列表进行替换。

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 (E e : snapShot) {
       result = f.apply(result, e);
    }
    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);
}

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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值