针对for、foreach以及迭代器的总结(针对集合删除元素)

前言

  针对for、foreach、迭代器的区别,这里不再详细叙述。只做简单的介绍,本篇针对循环删除集合的元素时会发生什么。

简单介绍区别

  直接上代码:

   public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("dabin");
        list.add("xiaobin");
        list.add("datoubin");
        list.add("dabin");
        list.add("dabin");
    System.out.println("foreach写法");
  for (String it : list) {
System.out.println(it);
  }
   System.out.println("for写法");
  for (int i = 0; i < list.size(); i++) {
   System.out.println(list.get(i));
  }
  System.out.println("迭代器写法");
  Iterator<String> it = list.iterator();
   while (it.hasNext()) {
   System.out.println(it.next());
  }
  }

  输出结果如图:

              

   如此就看出使用的区别的,

  然后就是这篇文章的重点了!!!!

循环打印集合删除元素问题

   就比如我要在集合中删除某些元素从三种方式去一一讲解:

  常见的for循环遍历中删除元素

    直接上代码:

 1 public static void main(String[] args) {
 2         List<String> list = new ArrayList<>();
 3         list.add("dabin");
 4         list.add("xiaobin");
 5         list.add("datoubin");
 6         list.add("dabin");
 7         list.add("dabin");
 8         for (int i = 0; i < list.size(); i++) {
 9             if ("dabin".equals(list.get(i))) {
10                 list.remove(i);
11             }
12         }
13         for (int i = 0; i < list.size(); i++) {
14             System.out.println(list.get(i));
15         }
16 }

 

    这时候输出:

    

    咦,我们就发现了,为什么还有一个dabin没有被删除呢?

    这个就要从for语句删除来讲了,

    这是因为在list通过for循环遍历中删除元素之后,后面的元素会上前替补前面元素的位置,这样就有可能会导致元素遍历过程中某些元素被漏掉,结果就出现了上面的那种结果。

    显然通过这种方式是不能精确地删除集合中所有满足条件的元素,刚才也说过是因为元素位置发生了变化导致了这种情况,那么我们可不可以换一种思路,倒序for循环遍历呢??将上面的代码改为倒序遍历,我们来试试康康:

 1     public static void main(String[] args) {
 2         List<String> list = new ArrayList<>();
 3         list.add("dabin");
 4         list.add("xiaobin");
 5         list.add("datoubin");
 6         list.add("dabin");
 7         list.add("dabin");
 8         for (int i = list.size() - 1; i >= 0; i--) {
 9             if ("dabin".equals(list.get(i))) {
10                 list.remove(i);
11             }
12         }
13         for (int i = 0; i < list.size(); i++) {
14             System.out.println(list.get(i));
15         }
16     }

    输出结果如下:

    咦,这时候就发现,成功了。good

   foreach循环遍历中删除元素

    现在就来试试康foreach了:

 1    public static void main(String[] args) {
 2         List<String> list = new ArrayList<>();
 3         list.add("dabin");
 4         list.add("xiaobin");
 5         list.add("datoubin");
 6         list.add("dabin");
 7         list.add("dabin");
 8 
 9         for (String it : list) {
10             if ("dabin".equals(it)) {
11                 list.remove(it);
12             }
13         }
14 }

 

 

呜呼,完蛋报错了,直接抛出了一个ConcurrentModificationException异常,

我们来看看这个这个异常是因为什么:

其实Foreach遍历是通过实现Iterable接口的类通过,那是因为foreach要依赖于Iterable接口返回的Iterator对象,所以从本质上来讲,Foreach其实就是在使用迭代器,在使用foreach遍历时对集合的结构进行修改,和在使用Iterator遍历时对集合结构进行修改本质上是一样的。所以同样的也会抛出异常,执行快速失败机制。

  那这个问题先留着,我们来先看看用迭代器遍历后能不能解决这个问题。

 

 迭代器遍历中删除元素

  直接上代码:

 1 public static void main(String[] args) {
 2         List<String> list = new ArrayList<>();
 3         list.add("dabin");
 4         list.add("xiaobin");
 5         list.add("datoubin");
 6         list.add("dabin");
 7         list.add("dabin");
 8         Iterator<String> it = list.iterator();
 9         while (it.hasNext()) {
10             String a = it.next();
11             if ("dabin".equals(a)) {
12                 list.remove(a);
13             }
14         }
15 }

 

 

     抛出了相同的异常,现在就要去看看源码,分析为什么抛出了

 1   private class Itr implements Iterator<E> {
 2         int cursor;       // index of next element to return
 3         int lastRet = -1; // index of last element returned; -1 if no such
 4         int expectedModCount = modCount;
 5 
 6         Itr() {}
 7 
 8         public boolean hasNext() {
 9             return cursor != size;
10         }
11 
12         @SuppressWarnings("unchecked")
13         public E next() {
14             checkForComodification();
15             int i = cursor;
16             if (i >= size)
17                 throw new NoSuchElementException();
18             Object[] elementData = ArrayList.this.elementData;
19             if (i >= elementData.length)
20                 throw new ConcurrentModificationException();
21             cursor = i + 1;
22             return (E) elementData[lastRet = i];
23         }
24 
25         public void remove() {
26             if (lastRet < 0)
27                 throw new IllegalStateException();
28             checkForComodification();
29 
30             try {
31                 ArrayList.this.remove(lastRet);
32                 cursor = lastRet;
33                 lastRet = -1;
34                 expectedModCount = modCount;
35             } catch (IndexOutOfBoundsException ex) {
36                 throw new ConcurrentModificationException();
37             }
38         }

 

通过查看源码发现原来检查并抛出异常的是checkForComodification()方法。在ArrayList中modCount是当前集合的版本号,每次修改(增、删)集合都会加1;expectedModCount是当前迭代器的版本号,在迭代器实例化时初始化为modCount。我们看到在checkForComodification()方法中就是在验证modCount的值和expectedModCount的值是否相等,所以当你在调用了ArrayList.add()或者ArrayList.remove()时,只更新了modCount的状态,而迭代器中的expectedModCount未同步,因此才会导致再次调用Iterator.next()方法时抛出异常。但是为什么使用Iterator.remove()就没有问题呢?通过源码的第34行发现,在Iterator的remove()中同步了expectedModCount的值,所以当你下次再调用next()的时候,检查不会抛出异常。
使用该机制的主要目的是为了实现ArrayList中的快速失败机制(fail-fast),在Java集合中较大一部分集合是存在快速失败机制的。
快速失败机制产生的条件:当多个线程对Collection进行操作时,若其中某一个线程通过Iterator遍历集合时,该集合的内容被其他线程所改变,则会抛出ConcurrentModificationException异常。
所以要保证在使用Iterator遍历集合的时候不出错误,就应该保证在遍历集合的过程中不会对集合产生结构上的修改。

 

你以为现在就结束了嘛?不不不

 1     public static void main(String[] args) {
 2         List<String> list = new ArrayList<>();
 3         list.add("dabin");
 4         list.add("xiaobin");
 5         list.add("datoubin");
 6         list.add("dabin");
 7         list.add("dabin");
 8         Iterator<String> it = list.iterator();
 9         while (it.hasNext()) {
10             String a = it.next();
11             if ("dabin".equals(a)) {
12                 it.remove();
13             }
14         }
15         for (int i = 0; i < list.size(); i++) {
16             System.out.println(list.get(i));
17         }
18 }

有没有人发现两者有什么不同呢?

 没错啦,这里是通过iterator的remove()方法来进行删除的,而不是通过list.remove()方法来删除,如果还是用list.remove(),依旧会出现上面那种问题。

有兴趣的同学可以去看看哦,

 

 所以现在总结一下,

通过遍历删除元素的话for和迭代器都是可以实现的,只有foreach不行,

 

 

posted @ 2019-09-28 01:05  对望  阅读(873)  评论(0编辑  收藏  举报