首页 » 编写高质量代码:改善Java程序的151个建议 » 编写高质量代码:改善Java程序的151个建议全文在线阅读

《编写高质量代码:改善Java程序的151个建议》建议72:生成子列表后不要再操作原列表

关灯直达底部

前面说了,subList生成的子列表是原列表的一个视图,那在subList执行完后,如果修改了原列表的内容会怎样呢?视图是否会改变呢?如果是数据库视图,表数据变更了,视图当然会变了,至于subList生成的视图是否会改变,还是从源码上来看吧,代码如下:


public static void main(String args){

List<String>list=new ArrayList<String>();

list.add("A");

list.add("B");

list.add("C");

List<String>subList=list.subList(0,2);

//原字符串增加一个元素

list.add("D");

System.out.println("原列表长度:"+list.size());

System.out.println("子列表长度:"+subList.size());

}


程序中有一个原始列表,生成了一个子列表,然后在原始列表中增加一个元素,最后打印出原始列表和子列表的长度,大家想一下,这段程序什么地方会出现错误呢?

list. add("D")会报错吗?不会,subList并没有锁定原列表,原列表当然可以继续修改。

难道有两个size方法?正确,确实是size方法出错了,输出结果如下:


原列表长度:4

Exception in thread"main"java.util.ConcurrentModifcationException

at java.util.SubList.checkForComodification(AbstractList.java:752)

at java.util.SubList.size(AbstractList.java:625)


什么?居然是subList的size方法出现了异常,而且还是并发修改异常?这没道理呀,这里根本就没有多线程操作,何来并发修改呢?这个问题很容易回答,那是因为subList取出的列表是原列表的一个视图,原数据集(代码中的list变量)修改了,但是subList取出的子列表不会重新生成一个新列表(这点与数据库视图是不相同的),后面在对子列表继续操作时,就会检测到修改计数器与预期的不相同,于是就抛出了并发修改异常。

出现这个问题的最终原因还是在子列表提供的size方法的检查上,还记得上面几个例子中经常提到的修改计数器吗?原因就在这里,我们来看看size的源代码:


public int size(){

checkForComodifcation();

return size;

}


其中的checkForComodification方法就是用于检测是否并发修改的,代码如下:


private void checkForComodification(){

//判断当前修改计数器是否与子列表生成时一致

if(l.modCount!=expectedModCount)

throw new ConcurrentModificationException();

}


expectedModCount是从什么地方来的呢?它是在SubList子列表的构造函数中赋值的,其值等于生成子列表时的修改次数(modCount变量)。因此在生成子列表后再修改原始列表,l.modCount的值就必然比expectedModCount大1,不再保持相等了,于是也就抛出了ConcurrentModificationException异常。

subList的其他方法也会检测修改计数器,例如set、get、add等方法,若生成子列表后,再修改原列表,这些方法也会抛出ConcurrentModificationException异常。

对于子列表操作,因为视图是动态生成的,生成子列表后再操作原列表,必然会导致“视图”的不稳定,最有效的办法就是通过Collections.unmodifiableList方法设置列表为只读状态,代码如下:


public static void main(Stringargs){

List<String>list=new ArrayList<String>();

List<String>subList=list.subList(0,2);

//设置列表为只读状态

list=Collections.unmodifableList(list);

//对list进行只读操作

doReadSomething(list)

//对subList进行读写操作

doReadAndWriteSomething(subList)

}


这在团队编码中特别有用,比如我生成了一个List,需要调用其他同事写的共享方法,但是有一些元素是不能修改的,想想看,此时subList方法和unmodifiableList配合着使用是不是就可以解决我们的问题了呢?防御式编程就是教我们如此做的。

这里还有一个问题,数据库的一张表可以有很多视图,我们的List也可以有多个视图,也就是可以有多个子列表,但问题是只要生成的子列表多于一个,则任何一个子列表就都不能修改了,否则就会抛出ConcurrentModificationException异常。

注意 subList生成子列表后,保持原列表的只读状态。