程序员最近都爱上了这个网站  程序员们快来瞅瞅吧!  it98k网:it98k.com

本站消息

站长简介/公众号

  出租广告位,需要合作请联系站长


+关注
已关注

分类  

暂无分类

标签  

暂无标签

日期归档  

JAVA集合为什么不能在foreach 循环中添加或删除元素?

发布于2021-06-12 14:01     阅读(472)     评论(0)     点赞(5)     收藏(1)


1. 编码强制规约

在《阿里巴巴Java开发手册》中,针对集合操作,有一项规定,如下:

【强制】不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁。

  1. public class SimpleTest {
  2. public static void main(String[] args) {
  3. List<String> list = Lists.newArrayList();
  4. list.add("1");
  5. list.add("2");
  6. list.add("3");
  7. list.add("4");
  8. //正例
  9. Iterator<String> iterator = list.iterator();
  10. while (iterator.hasNext()) {
  11. String item = iterator.next();
  12. if ("1".equalsIgnoreCase(item)) {
  13. iterator.remove();
  14. }
  15. }
  16. //反例
  17. for (String item : list) {
  18. if ("2".equals(item)) {
  19. list.remove(item);
  20. }
  21. }
  22. }
  23. }

2. 原因分析

在循环或迭代时,会首先创建一个迭代实例,这个迭代实例的expectedModCount 赋值为集合的modCount.   

每当迭代器使⽤ hashNext() / next() 遍历下⼀个元素之前,都会检测 modCount 变量与expectedModCount 值是否相等,相等的话就返回遍历;否则就抛出异常【ConcurrentModificationException】,终⽌遍历

如果在循环中添加或删除元素,是直接调用集合的add,remove方法【导致了modCount增加或减少】,但这些方法不会修改迭代实例中的expectedModCount,导致在迭代实例中expectedModCount 与 modCount的值不相等,抛出ConcurrentModificationException异常

但迭代器中的remove,add方法,会在调用集合的remove,add方法后,将expectedModCount 重新赋值为modCount,所以在迭代器中增加、删除元素是可以正常运行的。

可以参考ArrayList中的内部私有类Itr、ListItr的源码

  1. public Iterator<E> iterator() {
  2. return new Itr();
  3. }
  4. /**
  5. * An optimized version of AbstractList.Itr
  6. */
  7. private class Itr implements Iterator<E> {
  8. int cursor; // index of next element to return
  9. int lastRet = -1; // index of last element returned; -1 if no such
  10. int expectedModCount = modCount;
  11. Itr() {}
  12. //删除了一些代码
  13. public void remove() {
  14. if (lastRet < 0)
  15. throw new IllegalStateException();
  16. checkForComodification();
  17. try {
  18. ArrayList.this.remove(lastRet);
  19. cursor = lastRet;
  20. lastRet = -1;
  21. expectedModCount = modCount;
  22. } catch (IndexOutOfBoundsException ex) {
  23. throw new ConcurrentModificationException();
  24. }
  25. }
  26. final void checkForComodification() {
  27. if (modCount != expectedModCount)
  28. throw new ConcurrentModificationException();
  29. }
  30. }
  31. public E remove(int index) {
  32. rangeCheck(index);
  33. modCount++;
  34. E oldValue = elementData(index);
  35. int numMoved = size - index - 1;
  36. if (numMoved > 0)
  37. System.arraycopy(elementData, index+1, elementData, index,
  38. numMoved);
  39. elementData[--size] = null; // clear to let GC do its work
  40. return oldValue;
  41. }

3. 相关知识介绍

3.1. 什么是快速失败(fail-fast)?

快速失败(fail-fast) 是 Java 集合的⼀种错误检测机制。在使⽤迭代器对集合进⾏遍历的时候,在多线程下操作⾮安全失败(fail-safe)的集合类可能就会触发 fail-fast 机制,导致抛出ConcurrentModificationException 异常。 

另外,在单线程下,如果在遍历过程中对集合对象的内容进⾏了修改的话也会触发 fail-fast 机制。

举个例⼦:多线程下,如果线程 1 正在对集合进⾏遍历,此时线程 2 对集合进⾏修改(增加、删除、修改),或者线程 1 在遍历过程中对集合进⾏修改,都会导致线程 1 抛出ConcurrentModificationException 异常。

 

3.2. 什么是安全失败(fail-safe)呢?

采⽤安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,⽽是先复制原有集合内容,在拷⻉的集合上进⾏遍历。所以,在遍历过程中对原集合所作的修改并不能被迭代器检测到,故不会抛ConcurrentModificationException 异常。

 



所属网站分类: 技术文章 > 博客

作者:我爱编程

链接:http://www.javaheidong.com/blog/article/222092/6beba5d1ce07bb5855fb/

来源:java黑洞网

任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任

5 0
收藏该文
已收藏

评论内容:(最多支持255个字符)