`
tianlihu
  • 浏览: 311554 次
  • 性别: Icon_minigender_1
  • 来自: 石家庄
社区版块
存档分类
最新评论

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

阅读更多
本文版权归作者所有,仅供用来网上学习来用,读者可以收藏,请不要下载到本机或重新发布到其它网站

先看一个例子:
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) +" ");
		}
	}
}

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

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

  再试一下,结果正确了:
  10 30 50 70 90

  这种循环很常见,也很有效果,且效率很高。因为List的size方法只被调用了一次。在JDK的原码中,很多listener的遍历都是用的倒序。
0
0
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics