针对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不行,