普通循环:利用index实现
增强型循环:通过迭代器实现
示例代码:
public class ArrayListTest {
public static void main(String[] args) {
normalFor(getList()); //普通循环
iterator(getList()); //增强循环-迭代器
forEach(getList()); //增强循环-foreach方式
}
//普通循环
private static void normalFor(List list) {
for (int i = 0 ; i < list.size() ; i++){
if ("b".equalsIgnoreCase(list.get(i)) || "c".equalsIgnoreCase(list.get(i))){
list.remove(i);
}
}
System.out.println("normalFor:"+JSONObject.toJSONString(list));
}
//增强循环-迭代器
private static void iterator(List list) {
Iterator iterator = list.iterator();
while (iterator.hasNext()){
String str = (String) iterator.next();
if ("b".equalsIgnoreCase(str) || "c".equalsIgnoreCase(str)){
iterator.remove();
}
}
System.out.println("iterator:"+JSONObject.toJSONString(list));
}
//增强循环-foreach方式
private static void forEach(List list) {
for (String str : list){
if ("b".equalsIgnoreCase(str) || "c".equalsIgnoreCase(str)){
list.remove(str);
}
}
System.out.println("forEach:"+JSONObject.toJSONString(list));
}
private static List getList(){
List list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
return list;
}
}
输出
normalFor:["a","c","d","e"]
iterator:["a","d","e"]
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at com.qxy.collection.ArrayListTest.forEach(ArrayListTest.java:47)
at com.qxy.collection.ArrayListTest.main(ArrayListTest.java:21)
Process finished with exit code 1
从上边可以看出
类型
输出结果
普通循环
正常输出,结果错误
增强循环-迭代器
正常输出,结果正确
增强循环-foreach
报异常
普通循环
普通循环,底层是数组,在remove操作时,被删除元素的后边所有的元素,会往前挪挪一位。咱们还是看图,比较直观
当第一次删除时,此时的 i = 1,b正常删除,c、d、e此时都往前挪了一位,然后执行了 i+1 变成了2,也就是d 的位置,一直往后都没匹配到c,所以导致c为正常删除。
增强循环-迭代器
在分析之前,我们先来看看反编译之后的代代码
public class ArrayListTest {
...
private static void iterator(List list) {
Iterator iterator = list.iterator();
while(true) {
String str;
do {
if (!iterator.hasNext()) {
System.out.println("iterator:" + JSONObject.toJSONString(list));
return;
}
str = (String)iterator.next();
} while(!"b".equalsIgnoreCase(str) && !"c".equalsIgnoreCase(str));
iterator.remove();//不同的地方:调用迭代器的remove方法
}
}
private static void forEach(List list) {
Iterator var1 = list.iterator();
while(true) {
String str;
do {
if (!var1.hasNext()) {
System.out.println("forEach:" + JSONObject.toJSONString(list));
return;
}
str = (String)var1.next();
} while(!"b".equalsIgnoreCase(str) && !"c".equalsIgnoreCase(str));
list.remove(str);//不同的地方:调用list的remove方法
}
}
...
}
从上边的代码来看,迭代器 和 foreach 的方法很类似,唯一的区别就是 remove() 方法
迭代器调用的是 Iterator 类的 remove 方法
foreach调用的是 ArrayList类 的remove方法
那么我们去看下 他们各自 remove方法到底是怎么实现的
迭代器方式,那么需要先看 ArrayList.class
public Iterator iterator() {
return new Itr();
}
/**
* An optimized version of AbstractList.Itr
*/
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 void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification(); //第一步
try {
ArrayList.this.remove(lastRet); //第二步:调用list的remove方法
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount; //第三步:modCount是remove方法去维护更新,由于第一步中校验 modCount 和 expectedModCount 是否相当等
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
...
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
可以看到,list.iterator() 返回的是一个 Itr对象(ArrayList私有的实例内部类),执行 iterator.remove() 方法时,
第一步:先调用 checkForComodification() 方法,此方法作用:modCount 和 expectedModCount 是否相当
第二步:也就是foreach方式中调用的remove方法,在ArrayList内部的remove方法,会更新modCount属性
第三步:将更新后的modCount重新赋值给expectedModCount变量,看这里!!!看这里!!!相比于有一个更新操作,才通过了上边第一步的校验!!!
到此,你可能还想看看,ArrayList类中的remove方法
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
看到此方法中,有一个modCount++的操作,也就是说,modCount会一直更新变化。
总结:jdk源码离我们那么近,但是,总是这样完美的擦肩而过,以后要多啃啃,不要错过!!!