龙空技术网

集合遍历元素并删除的正确姿势

fkjavaer 107

前言:

现时小伙伴们对“java循环删除元素”都比较关切,兄弟们都想要知道一些“java循环删除元素”的相关资讯。那么小编也在网上收集了一些有关“java循环删除元素””的相关资讯,希望姐妹们能喜欢,兄弟们快快来了解一下吧!

Alibaba Java开发手册中指出,不要在 foreach 循环里进行元素的 remove/add 操作,那么进行这些操作到底会产生什么样的结果呢?

我们先来写个实例看看。

 public class TestForeach {     public static void main(String[] args) {         List<Integer> numbers = new ArrayList<>();         numbers.add(1);         numbers.add(2);         numbers.add(3);         for (Integer number : numbers) {             numbers.remove(number);         }     } }

再来看看执行结果:

根据报错提示,可以定位到报错的位置在java.util.ArrayList$Itr.next(ArrayList.java:997),并最终定位到java.util.ArrayList$Itr.checkForComodification()那么我们跳转过去看看源码。

可以看到,我们定位到了ArrayList中内部类Itr类中的next()方法,类Itr实现了迭代器接口Iterator,因此可以用来遍历集合的元素。

那么为什么我们使用增强for循环时,会进入到迭代器的next()方法中呢?我们来看看反编译后的class文件,揭开其真实面目。

原来,源代码在编译之后,实际上是使用迭代器来帮我们进行遍历的,这就足以说明为什么产生报错的地方是在Itr的next()方法处了。接下来我们再来查看next()方法。

果然,在方法的第一行处执行了,checkForComodification(),也就是最终报错的地方,从方法名可以大概知道,该方法的目的是检测集合是否被修改过。我们再跟进看看:

发现方法中就是在判断两个类变量值是否相等,那么这两个变量又来自何处,我们继续跟进源码:

从上面两张图可以得知,modCount来自AbstractList类,而ArrayList继承自AbstractList,因此可以直接使用该变量,该字段的注释的表明,该字段记录了集合结构被修改的次数,而迭代器被创建的时候将该字段赋值给了expectedModCount字段,这使得一开始它们的值是相等的。

这里猜测,我们在对集合元素进行删除操作时,修改了modCount的值,从而导致两个变量的值不相等,我们来一探究竟。

 public E remove(int index) {     Objects.checkIndex(index, size);     final Object[] es = elementData;      @SuppressWarnings("unchecked") E oldValue = (E) es[index];     fastRemove(es, index);      return oldValue; }
 private void fastRemove(Object[] es, int i) {     modCount++;     final int newSize;     if ((newSize = size - 1) > i)         System.arraycopy(es, i + 1, es, i, newSize - i);     es[size = newSize] = null; }

最终在fastRemove()执行了modCount++操作,因此而导致了抛出ConcurrentModificationException

那么在遍历集合时,我们正确的删除姿势是什么样的呢?

 public class TestForeach {     public static void main(String[] args) {         List<Integer> numbers = new ArrayList<>();         numbers.add(1);         numbers.add(2);         numbers.add(3);         Iterator<Integer> iterator = numbers.iterator();         while (iterator.hasNext()) {             iterator.next();             iterator.remove();         }     } }

正确的姿势就是使用迭代器的remove()方法。

 public void remove() {     if (lastRet < 0)         throw new IllegalStateException();     checkForComodification();      try {         ArrayList.this.remove(lastRet);         cursor = lastRet;         lastRet = -1;         expectedModCount = modCount;     } catch (IndexOutOfBoundsException ex) {         throw new ConcurrentModificationException();     } }

在该方法中,执行了expectedModCount = modCount;操作,从而避免了ConcurrentModificationException异常。

标签: #java循环删除元素