foreach原理和注意小坑,Iterator
1.foreach的原理
先看一段代码
public static void main(String[] args) {
ArrayList<Object> list = new ArrayList<>();
list.add("1");
list.add("2");
for (Object o : list) {
System.out.println(o);
}
}
我们通过编译之后,打开.class文件发现
public static void main(String[] args) {
ArrayList<Object> list = new ArrayList();
list.add("1");
list.add("2");
Iterator var2 = list.iterator();
while(var2.hasNext()) {
Object o = var2.next();
System.out.println(o);
}
}
原来for增强的foreach原来是通过迭代器实现的。
2.Foreach的坑
跟for循环相比,Foreach不需要知道size的大小,很方便对容器进行遍历操作。但是在对集合操作时就有问题了。看一段代码
public static void main(String[] args) {
ArrayList<Object> list = new ArrayList<>();
list.add("1");
list.add("2");
for (Object o : list) {
if ("1".equals(o)) {
list.remove(o);
}
}
//输出
for (Object o : list) {
System.out.println(o);
}
}
输出后我们发现,输出2
public static void main(String[] args) {
ArrayList<Object> list = new ArrayList<>();
list.add("1");
list.add("2");
for (Object o : list) {
if ("2".equals(o)) {
list.remove(o);
}
}
//输出
for (Object o : list) {
System.out.println(o);
}
}
我们只是把"1".equals(o)换成"2".equals(o),运行发现
抛出ConcurrentModificationException异常。这是为什么呢?
-
我们先了解几个概念
-
size:集合大小(指含有元素的个数)
-
modCount:每次集合变化都进行+1操作
-
expectedModCount: 期望的ModCount,用来判断集合是否变化
-
看一下ArrayList的remove方法,重点是modCount进行+1操作,size进行-1操作
public E remove(int index) { rangeCheck(index); //modCount进行+1操作 modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); //size进行-1操作 elementData[--size] = null; // clear to let GC do its work return oldValue; }
-
看一下hashNext(),判断当前cursor下标 与size的比较
public boolean hasNext() { return cursor != size; }
-
看一下Iterator的next()方法,
public E next() { //查看是否集合改变 checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; }
关键在于checkForComodification()
final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
发现在这进行modCount != expectedModCount判断,是否抛出异常。
现在我们来找问题
首先add(“1”),modCount++,再add(“1”),modCount++,现在modCount = 2,调用Iterator时,expectedModCount = modCount;所以expectedModCount = 2。
- 在第一种情况下:
第一次遍历:cursor = 0,可以进行remove(“1”)操作,modCount++,所以modCount = 3,size = 1;
第二次遍历:cursor = 1,进行hashNext判断发现,当前cursor 下标 与size相等,不进行遍历了,遍历结束。所以2根本没有进行删除的遍历,所以后面遍历输出2。
- 在第二种情况下:
第一次遍历:正常进入,cursor = 0;
第二次遍历:cursor = 1,进行remove(“2”)操作,modeCount++,所以modCount = 3,size = 1;
第三次遍历:cursor = 2,还是不等于size,进行next()方法,
这是checkForComodification(),发现modCount前面进行了+1操作,和预期的expectedModCount不相等,抛出异常。
那么如果要在进行删除操作怎么办
-
就要用Iterator的remove方法,关键是代码expectedModCount = modCount;
public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; //设置预期值和modCount相等 expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } }
那么要进行增加怎么办?
那就要用listIterator()去遍历集合了。