遍历集合时删除集合中的元素问题
使用迭代器Iterator遍历集合元素时,如果删除的元素不是倒数第二个数据,则会抛出ConcurrentModificationException异常
public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("a");list.add("b");list.add("c"); for (Iterator<String> it = list.iterator();it.hasNext();){ String first = it.next(); if ("a".equals(first)) { list.remove(first); } System.out.println(first); } }
以上代码运行结果如下图:
原因:由于迭代器只负责对各种集合所包含的元素进行迭代,它自己并没有保留集合元素。它判断是否还有下一个元素的标准很简单:如果下一步即将访问的元素的索引不等于集合的大小,就会返回true,否则,返回false。当程序使用迭代器遍历集合的倒数第2个元素时,下一步即将访问的元素的索引为size()-1。如果此时通过List删除集合的任意一个元素,将导致集合size()变成size()-1,这将导致hasNext()方法返回false,也就是说遍历会提前结束,永远访问不到最后一个元素。迭代器在获取下一个元素的next()方法中,会调用checkForComodification()方法来检查集合是否被修改:遍历之前使用expectedModCount保留该集合被修改的次数,每次获取集合的下一个元素之前,检查集合的当前修改次数modCount与遍历之前的修改次数expectedModCount是否相等,如果不相等就直接抛出ConcurrentModification异常。源码如下
hasNext()源码:
public boolean hasNext() { return cursor != size; }
next()方法源码:
public E next() { // 检查是否已修改 checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; } // 如果修改了,直接抛异常 final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
如果使用普通的for循环删除集合中元素,也会出现数据丢失问题:
List<String> list = new ArrayList<>(); list.add("a");list.add("b");list.add("c"); for (int i = 0;i<list.size();i++) { String first = list.get(i); System.out.println(first); if ("a".equals(first)) { list.remove(first); } } // 输出结果为 a c
上述代码为什么丢失了元素b呢?原因是:删除元素a之后,集合中剩余的元素会“整体搬家”,所在的角标都会往前移动一位,此时元素b的角标由1变成了0,元素c的角标由2变成了1,而变量i此时的值为1,因此访问时丢失了元素b
使用增强版的for循环会出现和Iterator遍历时相同的问题
List<String> list = new ArrayList<>(); list.add("a");list.add("b");list.add("c"); for (String value:list) { System.out.println(value); if ("a".equals(value)) { list.remove(value); } }
如果想删除集合中的某些元素,可以先定义一个变量,在遍历集合时,将需要删除的元素都添加到该变量中,在循环外统一删除。
List<String> list = new ArrayList<>(); list.add("a");list.add("b");list.add("c"); List<String> deleteList = new ArrayList<>(); for (String value:list) { if ("a".equals(value)) { deleteList.add(value); } } if (deleteList.size() > 0) { list.removeAll(deleteList); deleteList = null; } // 输出结果为 b c for (String value:list) { System.out.println(value); }