zoukankan      html  css  js  c++  java
  • 针对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不行,

     

  • 相关阅读:
    满20年程序员生涯-与大家分享最近7年的快速成长经历(上海市青浦区快递行业战斗7年奋斗史)
    格局 逐阶而上
    基础才是重中之重~BouncyCastle实现的DES3加密~java通用
    jenkins~Publish Over SSH实现分布式部署
    maven~为MANIFEST.MF文件添加内容
    maven~多个plugin相同phase的执行顺序
    java~jar防止反编译
    个人博客的简单通告
    SQL Server中datetimeset转换datetime类型问题浅析
    MySQL如何计算统计redo log大小
  • 原文地址:https://www.cnblogs.com/dblb/p/11601221.html
Copyright © 2011-2022 走看看