前言:由于上一个星期工作繁忙,利用上下班和晚上睡前空余的时间拜读了秦小波老师的《改善Java程序的151建议》,感觉廓然开朗,注意到了很多平时在编写代码中并不会注意的问题,甚至感觉自己对Java只是略懂皮毛,不足以登大雅之堂,特此与读者分享读书笔记,以下内容摘自《改善Java程序的151建议》一书和笔者的理解
Java高质量代码系列文章
面向对象篇:http://ray-yui.iteye.com/blog/1926984
数据类型篇:http://ray-yui.iteye.com/blog/1927251
字符串篇:http://ray-yui.iteye.com/blog/1927647
数组与集合(1):http://ray-yui.iteye.com/blog/1928170
数组与集合(2):http://ray-yui.iteye.com/blog/1930155
好吧,本来是非常繁忙的星期五,但笔者公司的DB服务器又down了,文章时间又来了,上一章由于时间限制只讲了一半,下面继续书接上回
10.子列表只是原列表的视图
List接口中,提供了subList方法,作用就是返回一个列表中的子列表,这个方法与String类型的substring有点类似,请观察以下代码
class="java">
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
List<Integer> subList = list.subList(0, 2);
System.out.println(subList); // 输出为1,2
subList.add(3); // 子列表增加元素
System.out.println(list); // 输出原列表,输出为1,2,3
}
我们可以把List接口中的subList想成和数据库中的视图一样,在数据库中虽然不建议我们对视图进行增删改,但确实是可以操作的,而且操作完也可以反映到表当中,subList也同理,它只是原列表的视图,所有的操作都将直接影响原列表,但subList和数据库视图有一点不同,下面将会进行说明
11.推荐使用subList处理局部列表
现在有一个需求,在一个长度为50的列表中删除位置是10~20的元素
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>();
// 增加50行
for (int i = 0, size = 50; i < size; i++) {
list.add(i);
}
// 方法1
for (int i = 10; i < 20; i++) {
list.remove(10);
}
// 方法2
list.subList(10, 20).clear();
System.out.println(list);
}
以上方法2,为什么要不停删除位置为10的元素呢?这是很容易忽略的问题,因为List当删除元素时,为了保持有序性,会移动元素替换被删除的位置,所以当位置为10的元素删除后,11的位置会替换上来..在上面已经提到,修改子列表会直接影响原列表,使用方法2的一行就能完成,是不是很方便?
12.生成子列表后不要再操作原列表
subList生成子列表是原列表的视图,而对子列表的改动会直接影响原列表,那若然是对原列表的更改呢?
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
List<Integer> subList = list.subList(0, 2);
// 原列表增加数据
list.add(3);
// 对比元素数量
System.out.println(list.size() == subList.size());
// 运行结果,抛出java.util.ConcurrentModificationException异常
}
看到这个异常,很多人会觉得很郁闷,并发修改异常?可我程序并没有运行在多线程的环境当中,而原因是因为,当创建subList时,list会将1个变量modCount传递到subList的构造参数当中,而当list进行修改时,就会对modCount进行累加,而subList在进行操作时,会检查modCount是否和list的一致,若然不一直就抛出异常,这就是原因,这也是subList和数据库视图不同的地方,表修改后直接反应到视图,而list修改后,不会反应到subList,所以在使用subList时,应该使用如下方式对list进行加锁
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
List<Integer> subList = list.subList(0, 2);
// 设置列表为只读状态
list = Collections.unmodifiableList(list);
// 再对列表进行操作
list.add(4);
//运行结果,抛出java.lang.UnsupportedOperationException异常
}
使用以上方式就可以明确表示,此列表是只读,当对列表进行操作时,就会抛出异常,增强了语义,而一个List可以有多个subList,但只要存在一个subList,就不能对原List进行操作.请谨记
13.使用Comparator进行排序
我们都知道,在集合类中要排序时,可以使用JDK提供的帮助工具,例如
Collections中的sort方法对List进行排序,亦可以使用Tree的数据结构来进行排序,例如Set接口中的TreeSet,TreeMap,使用Tree结构来进行排序时,是需要对象拥有比较性的,我们可以在实体类中实现Comparable<T>接口,但排序的方式是多变的,有时经过一段时间后,需求变了,需要按另外一个元素进行排序,但修改了已经稳定的实体类是不应该的,我们可以使用Comparator来进行排序,请看以下代码
public static void main(String[] args) {
// 使用匿名内部类方式实现
List<Integer> list = new ArrayList<Integer>();
Collections.sort(list, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
// TODO Auto-generated method stub
return 0;
}
});
// TreeMap实现
Map<String, Integer> treeMap = new TreeMap<String, Integer>(
new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return 0;
}
});
}
无论是Collections的sort方法和TreeMap,TreeSet都会有重载的方式,接受一个Comparator(比较器),此时有可以在这里进行元素比较的定义
14.使用binarySearch需要注意
binarySearch(二分查找)是性能较高的一种查找方式,通常我们使用的indexOf为顺序查找,当查找到匹配的值时马上返回,但若然此集合的元素数量多的话,使用顺序查找就性能就会降低,特别是当要查找的元素位于集合的末尾时,效率更低,而二分查找是将数据折半然后进行查找,没有找到指定元素时再折半,但需要注意,二分查找必须保持集合的有序性,所以在开发当中,若要使用二分查找,需先将集合进行排序.这点务必注意
15.集合中元素equlas和compareTo必须同步
在刚才提及的binarySearch和indexOf两种查找当中,有一点是非常不同的,indexOf的查找的时候是使用equlas来进行匹配,equlas返回为true时认为找到元素,而binarySearch是使用compareTo进行匹配,当compareTo返回0时认为找到元素,而当一个用户实体当中,equlas是使用用户名来重写equlas判断,而compareTo使用密码来比较,那将会导致不必要的错误
16.集合运算时,使用更优雅的方式
在集合的操作当中,经常会出现两个集合的 并集(or),交集(and),差集(not)等的操作,当然我们可以遍历两个集合,对比元素求出我们需要的并集交集差集等运算的结果,但这种方式处理,真的是最方便?最简单?最优雅?请看以下代码
public static void main(String[] args) {
List<Integer> list1 = new ArrayList<Integer>();
List<Integer> list2 = new ArrayList<Integer>();
// 并集操作
list1.addAll(list2);
// 交集操作,retainAll会删除list1中没有出现在list2中的元素
list1.retainAll(list2);
// 差集操作
list1.removeAll(list2);
// 无重复并集
list2.removeAll(list1);
list1.addAll(list2);
}
17.使用shuffle打乱列表
现在我们有一个需求,做一个扑克游戏,54张扑克存储到一个集合当中,每次发牌之前,都需要打乱一下扑克的顺序,这样的需求,我们可以有多种实现方式,使用Random来随机调整集合位置,或者集合的位置顺序交换,可有更优雅的方式吗?
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>();
// 你好,我是one-line君
Collections.shuffle(list);
}
18.多线程时使用Vetor或HashTable
通过查看JDK API可以发现,Vetor和HashTable是JDK1.0时已存在的类,他们是线程安全的,可在多线程的环境下使用
总结:
笔者在本文章中只从《改善Java程序的151建议》中提取部分进行归纳性叙述,推荐各位读者购买这本书,该书不仅从事例中学习,而且涉及到原理,底层的实现,不仅告诉你应该怎么做,还告诉你为什么要这样做.