浅谈 Java 加强For
2019-01-23 本文已影响0人
鸡蛋灌烧饼
一、问题引入
增强for遍历集合时,remove一个数据,竟然抛出了异常(ConcurrentModificationException),心中万马奔腾,这是怎么回事?
二、List 常用遍历方法
//普通for
for (int i = 0; i < list.size(); i++) {
...;
}
//加强for
for (String s : list) {
...;
}
//迭代器
Iterator<String> iterable = list.iterator();
while (iterable.hasNext()){
String s = iterable.next();
}
这里主要研究下加强for特点,下面写一个加强for并查看编译后的样子:
加强for和编译后文件
其实编译之后发现变成了 for + iterator由此发现以下几点
- 简单的书写风格还是要编译成迭代器的
- 底层使用的是迭代器实现的,每次循环都通过迭代器的hasNext()方法判断是否有下一项,通过迭代器的next取值,并赋值给临时变量的
- 如果随意在for中执行list的add或者remove方法的话会引起崩溃(ConcurrentModificationException),实际上使用迭代器自带的删除就没什么问题了
三、看源码
查看ArrayList#ItrAPI
源码如下
private class Itr implements Iterator<E> {
int cursor;
int lastRet = -1;
int expectedModCount;
Itr() {
this.expectedModCount = ArrayList.this.modCount;
}
public boolean hasNext() {
return this.cursor != ArrayList.this.size;
}
//获取下一个元素
public E next() {
this.checkForComodification();
int var1 = this.cursor;
if (var1 >= ArrayList.this.size) {
throw new NoSuchElementException();
} else {
Object[] var2 = ArrayList.this.elementData;
if (var1 >= var2.length) {
throw new ConcurrentModificationException();
} else {
this.cursor = var1 + 1;
return var2[this.lastRet = var1];
}
}
}
public void remove() {
if (this.lastRet < 0) {
throw new IllegalStateException();
} else {
this.checkForComodification();
try {
//迭代器调List的删除方法
ArrayList.this.remove(this.lastRet);
this.cursor = this.lastRet;
this.lastRet = -1;
//同步迭代器本身的标识
this.expectedModCount = ArrayList.this.modCount;
} catch (IndexOutOfBoundsException var2) {
throw new ConcurrentModificationException();
}
}
}
public void forEachRemaining(Consumer<? super E> var1) {
Objects.requireNonNull(var1);
int var2 = ArrayList.this.size;
int var3 = this.cursor;
if (var3 < var2) {
Object[] var4 = ArrayList.this.elementData;
if (var3 >= var4.length) {
throw new ConcurrentModificationException();
} else {
while(var3 != var2 && ArrayList.this.modCount == this.expectedModCount) {
var1.accept(var4[var3++]);
}
this.cursor = var3;
this.lastRet = var3 - 1;
this.checkForComodification();
}
}
}
/**此方法会比较ArrayList的标识和迭代其中是否相同,
*不相同直接抛出异常,迭代器的方法都会调用这个方法
*/
final void checkForComodification() {
if (ArrayList.this.modCount != this.expectedModCount) {
throw new ConcurrentModificationException();
}
}
}
四、问题原因解析
先了解一下List中有一个int modCount作为标识记录集合的修改次数,每次remove,add等都会累加1,所以:
- 迭代循环中,直接修改List结构:在开始迭代时,会把当前的标识赋值到迭代器int expectedModCount,在迭代过程中,对List结构修改List的标识未同步到迭代器中,所以在执行遍历的时候会抛出异常。
- 而使用迭代器中的删除则不一样,迭代器中删除之后,会修改List的标识,然后同步迭代器本身的标识,所以不会抛出异常。
- PS :modCount 是记录这个列表在结构上被修改的次数。
- 集合的一种错误机制fail-fast 了解一下.