驽马十驾 驽马十驾

驽马十驾,功在不舍

目录
对List遍历过程中添加和删除的思考
/    

对List遍历过程中添加和删除的思考

开篇

平时开发过程中,不少开发者都遇到过一个问题:在遍历集合的的过程中,进行add或者remove操作的时候,会出现2类错误:

  • java.util.ConcurrentModificationException for in遍历过程中add/remove导致的错误
  • java.lang.IndexOutOfBoundsException 越界错误,for循环的时候删除元素。

最佳实践

  • add操作:利用原生的for循环

  • remove操作利用foreach操作。

    如下所示:

      //OK,利用 iterator 进行 remove 擦破自拍
      @Test
      public void testRemove() {
          Iterator<String> iterator = list.iterator();
          while (iterator.hasNext()) {
              if ("3".equals(iterator.next())) {
                  iterator.remove();
              }
          }
          System.out.println(list);
      }
    
      //OK 利用for循环进行add操作
      @Test
      public void testForAdd() {
          for (int i = 0, length = list.size(); i < length; i++) {
              if (list.get(i).equals("2")) {
                  list.add("2");
              }
          }
      }
    
  • 对于remove操作,重点是iterator.hasNext() iterator.remove()

  • 对于add操作,重点是在for循环的初始化的步骤初始化for (int i = 0, length = list.size(); xxx;xxx) ,请注意length的赋值只会进行一次。

经典错误1

如下代码本意是:通过iterator的方式从头到尾变遍历list中的元素。

  @Test
  public void testIteratorRemove2() {
      while (list.iterator().hasNext()) {
        System.out.println(list.iterator().next());
      }
  }

但是该段代码永远都会输出 list的第一元素,为什么?关键错误在链式写法上:

while (list.iterator().hasNext()) {}

每次循环时候先调用了list.iterator() 在该方法中每次都是重新new了一个新的对象

  public Iterator<E> iterator() {
      return new Itr();
  }

所以每一次都是一个新的遍历对象,所以输出第一个元素。

那么为什么每次都要new一个新的Itr()?我猜想应该是为了并发的读,每次读的都是一份独立的数据,避免多个并发读的时候,出现当前指针问题。

处理办法:将list.iteraotr() 放在外面即可,保证循环中循环的是1个对象。

  @Test
  public void testIteratorRemove2() {
      Iterator<String> iterator = list.iterator();
      while (iterator.hasNext()) {
          if ("3".equals(iterator.next())) {
              iterator.remove();
          }
      }
      System.out.println(list);
  }

经典错误2

 	// 死循环
  @Test
  public void testForAdd2() {
      for (int i = 0; i < list.size(); i++) {
          if (list.get(i).equals("3")) {
              list.add("3");
          }
      }
  }

当if条件满足的时候,该方法永远不会结束,为什么?

对于for循环 for (int i = 0; i < list.size(); i++)有3个部分,第一个部分为初始化,只执行一次。第二个部分每次都会执行,第三个部分也是每次都会执行。

上述问题的第二步会导致无限循环:因为for中每一次循环都会在list添加了一个元素,每次步进为1,内部元素也是每次都加1.

如何处理该问题: list.size()放在第一部分,第一部分只初始化一次。

  //OK 利用for循环。
  @Test
  public void testForAdd() {
      for (int i = 0, length = list.size(); i < length; i++) {
          if (list.get(i).equals("2")) {
              list.add("2");
          }
      }
  }

经典错误3

  //java.lang.IndexOutOfBoundsException: Index: 2, Size: 2
  @Test
  public void testForRemove() {
      for (int i = 0, length = list.size(); i < length; i++) {
          if (list.get(i).equals("3")) {
              list.remove("3");
          }
      }
  }

该错误在list.add("3")的时候就不会发生该错误,具体原因是什么?留给你思考!

不积跬步,无以至千里。不积小流,无以成江海。