欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

五、编写高质量的代码—数组和集合(笔记)

程序员文章站 2022-06-16 16:18:47
...

本博文为《编写高质量代码—改善Java程序的151个建议》一书的阅读笔记。该书从很多方面给予了编写高质量代码的宝贵经验。而且该书应该是那种开发经验越丰富,体会越深的书籍。在阅读过程中,从该书中收获良多,这里主要作下书籍笔记,有体会的地方加点自己的想法。受限于知识水平,部分内容还没能深刻体会,所以更多更好的内容和具体实例还需要从书中去找寻。

一、性能考虑,数组是首选

在Java中数组虽然没有List、Set、Map这些集合类用起来方便,但是在基本类型处理方面,数组还是占优势的,而且集合类的底层也是通过数组实现的。所以在性能要求高的场景中推荐使用数组而不是集合。

二、若有必要,使用变长数组

Java中的数组是定长的,声明后就不可以改变长度,在使用中非常不方便。但是我们可以使用Arrays.copyOf实现数组动态扩容,这样既保证了效率,也提高了可用性。其实集合类的动态扩容也是同理。

三、警惕数组的浅拷贝

Arrays.copyOf方法产生的数组是一个浅拷贝,与序列化的浅拷贝、数组的clone方法、集合的clone方法一样都是浅拷贝:基本类型拷贝值,其他则拷贝引用。因此拷贝对象引用的值的修改也会影响被拷贝的对象的值,因为引用的是同一个地址。

四、在明确的场景下,为集合指定初始容量

集合类可以支持动态扩展长度,使用很方便,不过需要注意的是集合类实际存储使用的是数组,动态扩容也是通过Arrays.copyOf进行的。执行add操作时若默认长度不够时,会将集合的长度增加一定的倍数或长度(比如ArrayList会增加1.5倍),此时在数据量较大时进行拷贝会非常耗资源,效率也很低。
出于性能及资源使用考虑,在集合初始容量已知时,要直接初始为对应长度;在集合初始容量大概知道范围时,可以初始一个比最大值多一点的长度。

五、避开基本类型数组转换列表陷阱

书中举了一个非常好的例子,如下代码

int [] data = {1,2,3,3}; 
System.out.println(Arrays.asList(data).size()); 

 结果打印出的结果却只有一条,因为asList方法的参数为泛型参数,而基本类型不能作为泛型参数,而int数组是一个对象,同时也是可以泛型化的,所以asList(data)最后实际是把数组对象转换成列表了,结果打印就是1条。
所以一定要注意基本数据类型数组不能做为asList的输入参数,否则会引起程序逻辑混乱。

六、asList方法产生的List对象不可更改

Arrays.asList方法产生的List对象为Arrays类中的静态内部类ArrayList,其修改内容的方法(add、remove)是继承自父类的,而父类的方法仅仅是抛出异常,所以一定要注意asList产生的不可更改,不然会抛异常。

七、不同的列表选用不同的遍历算法

对于支持随机存储(实现RandomAccess接口)的列表,使用下标遍历要比使用foreach遍历快些,在数据量大时尤为明显。而对于顺序存储的列表(例如LinkedList)此时使用foreach又比使用下标遍历效率高些。
具体原因与遍历的实现代码有关,foreach是通过iterator进行遍历的,可以这么理解随机遍历的列表本来就希望下标访问,所以下标访问更快。而有序列表是有顺序的,所以通过foreach一个个迭代效率更高。当然实际原因是实现逻辑的问题。

八、频繁插入和删除时使用LinkedList

这个在数据结构里面肯定提到过,ArrayList这种随机列表随机存取效率高,而LinkedList这种有序列表插入和删除效率更高。即对于列表增、删操作多的应该选用LinkedList,而对于列表修改多的应该选用ArrayList。

九、判断列表相等时只用关心集合是否相等

集合元素进行相等判断(equals)时,可能是不同的集合类型,但是只要集合里面的元素相等则两个集合相等。

十、子列表只是原列表的一个视图

subList产生的列表只是一个视图,所有列表的改动都会直接作用于原列表上。

十一、生成子列表后不要再操作原列表

通过subList生成子列表后,如果再操作原列表会导致subList产生的列表调用size方法时抛出异常。对于子列表,因为视图是动态生成的,生成子列表后再操作原列表,必须导致视图的不稳定性,所以生成子列表后,最好是通过Collections.unmodifiableList方法设置列表为只读状态,这样就能避免错误的发生。

十二、不推荐使用binarySearch对列表进行检索

对一个列表进行检索时,使用的最多的是indexOf方法,该方法是通过遍历进行查找的;Collections类还提供一个binarySearch方法进行检索,该方法是通过二分查找法进行查找的,但是二分查找法有一个前提需要注意,就是列表必须是有序的,不然查找的结果不准确。 因此对于有序列表,binarySearch方法可以优先考虑,而对于无序列表不推荐使用。

十三、集合中的元素必须做到compareTo与equals同步

如果对象实现了compareTo方法就应该覆写equals方法,确保两者同步,不然可能产生逻辑混乱。

十四、集合运算时使用更优雅的方式

例如求并集可以直接使用list1.addAll(list2);求交集可以直接使用list1.retainAll(list2);求差集(属于A但不属于B的集合叫做A与B的差集);求不重复的集合(A与B不重复的集合为A与B的并集去掉一份重复的A与B的交集,如果交集有的话)可以先删后加:list2.remove(list1),list1.addAll(list2)

十五、多线程使用Vector或HashTable

 Vector是ArrayList的多线程版本,HashTable是HashMap的多线程版本。在多线程环境下更适用。