在集合类的遍历时操作需要谨慎

[b][size=medium][color=red]本文版权归作者所有,仅供用来网上学习来用,读者可以收藏,请不要下载到本机或重新发布到其它网站[/color][/size][/b]

先看一个例子:

import java.util.ArrayList;
import java.util.List;

public class CollectionOperation {

public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 10; i++) {
list.add(String.valueOf(i));
}

for (String str : list) {
if (str.equals("5")) {
list.remove(str);
}
}
}
}

这个程序可以通过编译,但在运行时会出现异常,异常信息如下:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
at java.util.AbstractList$Itr.next(AbstractList.java:343)
at CollectionOperation.main(CollectionOperation.java:12)

为什么会出现这种异常呢?因为在执行到第14行时,list的长度发生了变化,由原来的10变为9。第12行的for (String integer : list)这种循环是在JDK5以后才支持的。而这种循环对于集合类来说,会生成一个迭代对象Iterator,然后进行遍历的。在每次循环之前,它会检查集合类当前的长度是否发了变化,如果发生变化就会抛出如上的异常。对于ArrayList, 这段检查的代码是这样的(此段代码是写在AbstractList类的内部类Itr中):

public E next() {
checkForComodification();
try {
E next = get(cursor);
lastRet = cursor++;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}

final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}



因此,这种JDK5新规范的循环的写法,虽然简单,但是如果有对集合类对象有添加和删除的操作时,会出现这种异常。这时需要用常归的循环的写法了。

import java.util.ArrayList;
import java.util.List;

public class CollectionOperation {

public static void main1(String[] args) {
int size = 10;
List<String> list = new ArrayList<String>();
for (int i = 0; i < size; i++) {
list.add(String.valueOf(i));
}

for (int i = 0; i < size; i++) {
String str = list.get(i);
if (str.equals("5")) {
list.remove(str);
}
}
}
}

我们写程序一般很少在遍历的时候添加元素,多数是在遍历的时候,找到匹配的元素,然后进行删除操作。就像上面的例子一样,但是细心的朋友会发现,上面的例子有隐患存在。重新更改一下上面的例子,这个隐患就会暴露出来。

import java.util.ArrayList;
import java.util.List;

public class CollectionOperation {

public static void main1(String[] args) {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 10; i++) {
list.add(String.valueOf(5));
}

for (int i = 0; i < list.size(); i++) {
String str = list.get(i);
if (str.equals("5")) {
list.remove(str);
}
}

for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
}

只更改了一下第10行的值为5,也就是List中存放的都是5,对于后面的删除操作,我们本来是打算删除列表中值等于5的元素,即删除List中的全部元素,运行一下程序,我们发现,并没有完全删除,而是剩下了5个元素。
再看一个更奇怪的例子,还是这样的循环,我们删除一个数列上偶数位上的数,只留奇数位上的数。例如:原数列为10、20、30、40、50、60、70、80、90、100,则偶数位上的数为20、40、60、80、100,删除后剩余的数为:10、30、50、70、90。一般人会写出下面的错误程序:

public class CollectionOperation {

public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>();
for (int i = 0; i < 10; i++) {
list.add((i + 1) * 10);
}

for (int i = 0; i < list.size(); i++) {
if ((i + 1) % 2 == 0) {
list.remove(i);
}
}

for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i) +" ");
}
}
}

猛一看这个程序,是对的,可是一运行出来结果就不对。这个程序的结果是:
[color=darkred]10 30 40 60 70 90 100 [/color]

这个结果并不是我们想要的,但是到底哪里出错了?因为是因为集合类List为可变长的数组,当删除中间一个元素后,这个被删除元素的后面的元素会向前移动一个,即元素的索引变小了,但是循环变量i是没有变化的,这时再向后遍历时,会漏掉刚删除那个元素上新移进的元素。这样一直往复,直到结束。
怎样解决这个问题呢?
改换一下思路,因为删除元素的时候,后面的元素都是向前移动,因此,我们这样循环:

for (int i = list.size(); i >= 0; i--) {
if ((i + 1) % 2 == 0) {
list.remove(i);
}
}

再试一下,结果正确了:
[color=darkred]10 30 50 70 90 [/color]

这种循环很常见,也很有效果,且效率很高。因为List的size方法只被调用了一次。在JDK的原码中,很多listener的遍历都是用的倒序。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值