第25条:列表优先于数组

第25条:列表优先于数组

看到这个标题的时候,我有个疑惑:是不是优先使用列表,能用列表就尽量用列表吗?我觉得,之所以说列表优于数组,应该是在某些语法设计方面,列表有比较大的优势。但是要具体问题具体分析,数组也有自身的优势(对于固定长度的序列,性能更加高),而且在某些情况下,列表的所谓的优势并不能发挥。

数组是协变且可以具体化的;泛型是不可变的且可以被擦除的(不可具体化的)。
**

协变与不可变类型

数组是协变类型,指继承关系的。
泛型是不可变类型(也就是final类型),没有继承关系。无限制通配符类型是一个特例。

//这段代码在运行时出错
Object[] o = new Long[1];
o[0] = "I don't fit it";

//这段代码在编译时出错
List<Object> o = new ArrayList<Long>();
o.add("I don't fit it";

这并不意味着泛型是有缺陷的,而实际上可以说数组才是有缺陷的。利用数组,你会在运行时发现所犯的错误,而使用列表,则可以在编译的时候发现错误。我们当然希望在编译的时候发现错误,这个也是列表优于数组的一个方面。

可具体化与不可具体化

数组是可以具体化的。可以具体化意味着运行时包含的信息比编译期包含的信息多。
泛型是不可以具体化的,反之,在运行时包含的信息会更少,因为编译后会被擦除(替换成Object)。

其实以上例子已经很好的说明了可具体化与不可具体的区别。数组会在运行时才知道并检查他们的元素类型约束。相比之下,泛型则是通过擦除来实现的。因此泛型只在编译时强化他们的类型信息,并在运行时丢弃他们的元素类型信息。擦除就是使泛型可以与没有使用泛型的代码随意进行互用。

无法很好混用列表和数组

由于上述这些根本的区别,因此数组和列表无法很好地混合使用。例如创建泛型、参数化类型或者类型参数的数组都是不合法的。如new List[]、new List[] 和new E[],**但是创建无限制通配符类型的数组是合法的。**因为泛型数组不是类型安全,这与数组的可具体化的特性有关,即编译期正常进行类型转换,而在运行时报错。这就违背了泛型系统提供的基本保证。

public static void main(String[]args){
	List<String>[] strArray = new List<String> [10];//假设允许泛型数组
    List<Integer> intList = new ArrayList<Integer>();
		
	Object[] obj = strArray;
    //将intList存入了strArray中。 与刚才的协变的例子是一样的,
    // 运行时也不会报错因为擦除了类型
	obj[0] = intList;
		
	String str = (String) strArray[0].get(0);// 运行时装换错误。
}

混用列表和数组往往出现警告,例如Arrays.asList,因为禁止创建泛型数组,泛型一般不可能返回他的元素类型的数组,所以意味着在结合使用可变参数方法和泛型是会出现令人费解的警告。这是由于每当调用可变参数方法时,就会创建一个数组来存放可变参数。想要禁止警告的出现,可以添加@SuppressWarnings,或者避免在API中混用泛型和可变参数。

当你得到泛型数组创建错误时,最好的解决方法通常是优先使用列表,而不是数组类型E[]。这样可能损失一些性能或者简洁性,当时换回的是更高的类型安全性和互用性。

案例

需求:在执行运算之前需要对列表进行锁定(备份)。

数组列表混用:

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, e);  
    return result;
}

会有一条警告,编译器无法在编译时知道E是什么,所以是一个非受检的警告。
结果表明,他可以运行,但是不安全。通过微小的修改,就可以让他在没有包含显式转换的行上抛出ClassCastException异常。举个例子啊!!!!

用列表替代数组:

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

这个版本有些冗长,但是可以确保在运行时不会得到ClassCastException异常。

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值