> 文档中心 > Java遍历中的删除操作

Java遍历中的删除操作

目录

前言

第一种:普通for循环

第二种:利用迭代

总而言之


前言

        在遍历Collection的过程中删去部分元素的情景是非常常见的,但是利用Java编写这样的程序,常常会出现一些让人很费解的问题。本文将阐释这些问题的产生原因以及解决办法。

        不妨以List作为例子,当我们需要对一个字符串数组删去其中包含"6."前缀的元素时,我们下意识会编写出这样的代码:

  • 第一种:普通for循环

import java.util.ArrayList;import java.util.Iterator;import java.util.List;public class iterator {    public static void main(String[] args) { List list = new ArrayList(); list.add("6.045"); list.add("6.005"); list.add("6.813"); System.out.println("删除前:" + list); for(int i=0;i<list.size();i++) {     String temp = list.get(i);     if(temp.startsWith("6.")) //删去其中包含"6."前缀的元素  //list.remove(i);  list.remove(temp); //两种方法效果都一样 } System.out.println("删除后:" + list);    }}

        这样的写法我们在C/C++里经常用,无非是多使用了Java自带的List.remove()的方法删去元素罢了。我们很当然的觉得程序运行的结果一定是:list中所有元素都被删除,然而....

        居然有一个元素没有被删除!这是为什么??

        最开始时,i=0,list.size()=3,temp=list[0]=6.045,包含"6."前缀,删去,i++;删除后,list中剩余所有元素都向前移了一位(索引-1:类似于取出队首元素,其他元素自动前进补齐空缺),此时i=1,list.size()=2,temp=list[0]=6.813,包含"6."前缀,删去,i++;此后list.size()=1,循环结束

        所以,其实问题出在了List.remove()方法使用后,元素的索引改变导致某些元素未被访问到,更改方式其实很简单——倒序遍历数组

import java.util.ArrayList;import java.util.Iterator;import java.util.List;public class iterator {    public static void main(String[] args) { List list = new ArrayList(); list.add("6.045"); list.add("6.005"); list.add("6.813"); System.out.println("删除前:" + list); for(int i = list.size() - 1; i >= 0; i--) {     String temp = list.get(i);     if(temp.startsWith("6."))  list.remove(i);  //list.remove(temp); } System.out.println("删除后:" + list);    }}

 

        好耶,和预想的结果相同!!

  • 第二种:利用迭代器

        如果是接触过迭代器的小伙伴,也可能写出这样的代码

import java.util.ArrayList;import java.util.Iterator;import java.util.List;public class iterator {    public static void main(String[] args) { List list = new ArrayList(); list.add("6.045"); list.add("6.005"); list.add("6.813"); System.out.println("删除前:" + list); Iterator ite = list.iterator(); //迭代器 while (ite.hasNext()) {     String next =  ite.next();     if(next.startsWith("6."))  list.remove(next); } System.out.println("删除后:" + list);    }}

        或者引入写法比较简单的增强for循环【实际就是简化版的迭代器,底层实现相同】

import java.util.ArrayList;import java.util.Iterator;import java.util.List;public class iterator {    public static void main(String[] args) { List list = new ArrayList(); list.add("6.045"); list.add("6.005"); list.add("6.813"); System.out.println("删除前:" + list); for(String s:list) {     if(s.startsWith("6."))  list.remove(s); } System.out.println("删除后:" + list);    }}

        这样的写法真是方便又高级,但是怎么会报错啊...

        运行结果中抛出java.util.ConcurrentModificationException异常信息。这是因为触发了集合中并发修改的异常,接下来我们通过源码对抛出异常的原因进行剖析。 

    public Iterator iterator() { return new Itr();    }

        在ArrayList集合的Iterator方法中,是通过返回Itr对象来获得迭代器的。ItrArrayList的一个内部类,它实现了Iterator接口,代码如下:

 private class Itr implements Iterator { int cursor;// index of next element to return int lastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount; Itr() {} public boolean hasNext() {     return cursor != size; } @SuppressWarnings("unchecked") 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]; } 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();     } }

        ModCount定义在AbstractList接口中,初始值为0,定义如下:

 protected transient int modCount = 0;

        ModCount是版本号,在对集合进行变更操作(增加、删除、修改等)的时候会对版本号进行 +1 操作。 

结合上述代码进行抛出 java.util.ConcurrentModificationException 异常的解释。
①初始化ArrayList,添加三次元素,即三次调用add()方法,进行三次 modCount++ ; 此时,modCount = 3 , size = 3
②初始化Iterator迭代器进行循环,此时,expectedModCount = modCount = 3 ,cursor=0,lastRet=−1
③进行hasNext判断,cursor != size;成立,进入循环
④调用next()方法,首先进行checkForComodification()校验,modCount==expectedModCount,校验通过,返回值,此时lastRet=0;cursor=1

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

⑤调用集合remove()方法,modCount++ 此时modCount = 4 ; size = 2 

⑥再次调用hasNext()方法判断,cursor != size;成立,进入循环

⑦调用next()方法进行校验,modCount ! = expectedModCount ,校验未通过,抛出java.util.ConcurrentModificationException异常

 总结:

        修改方法也很简单,使用迭代器自带的remove方法即可

import java.util.ArrayList;import java.util.Iterator;import java.util.List;public class iterator {    public static void main(String[] args) { List list = new ArrayList(); list.add("6.045"); list.add("6.005"); list.add("6.813"); System.out.println("删除前:" + list); Iterator ite = list.iterator(); while (ite.hasNext()) {     String next =  ite.next();     if(next.startsWith("6."))  ite.remove(); //迭代器remove方法 } System.out.println("删除后:" + list);    }}

        可以看见,结果与预期相同,终于解决所有问题啦!

总而言之

        可行的办法分为两类:(1)普通for循环+倒序遍历+List.remove()

                                            (2)迭代器(非增强for循环)+迭代器remove()

参考文章

Java迭代器详解,看这一篇就够了_WYSCODER的博客-CSDN博客_java迭代器Java迭代器详解,看这一篇就够了!https://blog.csdn.net/sheng0113/article/details/122712947