你走不出劳碌的圈子,永远不晓得里面的世界有多精彩。
前言
置信大家必定都看过阿里巴巴开发手册,而在阿里巴巴开发手册中明确的指出,不要再foreach循环外面进行元素的add和remove,如果你非要进行remove元素,那么请应用Iterator形式,如果存在并发,那么你肯定要抉择加锁。
foreach
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
for (String s : list) {
if ("b".equalsIgnoreCase(s) || "c".equalsIgnoreCase(s)) {
list.remove(s);
}
}
System.out.println(JSONObject.toJSONString(list));
}
输入后果:
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 list.Test03.main(Test03.java:23)
Process finished with exit code 1
异样起因
咱们从异样信息动手,at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
异样信息中的909行也就是从这里开始的,大家必定会想他在比拟两个值 modCount
和expectedModCount
,那么这两个变量是什么呢?
其实说白了,他们就是来示意批改次数的变量,其中modCount
示意汇合的批改次数,这其中包含了调用汇合自身的add办法等批改办法时进行的批改和调用汇合迭代器的批改办法进行的批改。而expectedModCount
则是示意迭代器对汇合进行批改的次数。
而 expectedModCount
是个什么鬼?从ArrayList 源码可知,这个变量是一个局部变量,也就是说每个办法外部都有expectedModCount
和 modCount
的判断机制,进一步来讲,这个变量就是 预期的批改次数,而这个判断机制,很多人都会间接通知你说fail-fas
机制,你说这个机制,然而很多人都晓得,你想解释明确他,须要点功夫的,了解起来必定更须要花点工夫的。
先来看看反编译之后的代码,如下:
public static void main(String[] args) {
List<String> list = new ArrayList();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
Iterator var2 = list.iterator();
while(true) {
String s;
do {
if (!var2.hasNext()) {
System.out.println(JSONObject.toJSONString(list));
return;
}
s = (String)var2.next();
} while(!"b".equalsIgnoreCase(s) && !"c".equalsIgnoreCase(s));
list.remove(s);
}
}
看外面应用的也是迭代器,也就是说,其实 foreach 每次循环都调用了一次iterator
的next()
办法,这也是为什么咱们从堆栈信息看到的起因。
foreach形式中调用的remove
办法,是ArrayList外部的remove
办法,会更新modCount
属性
到此,你可能还想看看,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
会始终更新变动。
而这时候咱们就能够这么了解了,咱们第一次迭代的时候 AA != DD ,间接迭代第二次,这时候就相等了,执行remove()
办法,这时候就是modCount++
,再次调用next()
的时候,modCount = expectedModCount
这个就不成立了,所以异样信息呈现了,其实也能够了解为在 hasNext()
外面,cursor != size
而这时候就会呈现谬误了。
也就是说 remove
办法它只批改了modCount
,并没有对expectedModCount
做任何操作。
能够看出,迭代器调用的是 Iterator 类的 remove
办法,foreach调用的是 ArrayList类 的remove
办法,这是惟一的区别
迭代器
为什么阿里巴巴的标准手册会这样子定义?
它为什么举荐咱们应用 Iterator呢?
间接应用迭代器会批改expectedModCount
,而咱们应用foreach的时候,remove办法它只批改了modCount
,并没有对expectedModCount
做任何操作,而Iterator就不会这个样子。
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if ("b".equalsIgnoreCase(item) || "c".equalsIgnoreCase(item)) {
iterator.remove();
}
}
System.out.println("iterator:" + JSONObject.toJSONString(list));
}
输入后果:
iterator:["a","d","e"]
Process finished with exit code 0
能够看出后果是正确的,上面咱们来剖析一下:
先来看看反编译之后的代码:
public static void main(String[] args) {
List<String> list = new ArrayList();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
Iterator iterator = list.iterator();
while(true) {
String item;
do {
if (!iterator.hasNext()) {
System.out.println("iterator:" + JSONObject.toJSONString(list));
return;
}
item = (String)iterator.next();
} while(!"b".equalsIgnoreCase(item) && !"c".equalsIgnoreCase(item));
iterator.remove();
}
}
次要察看remove()
办法的实现,那么须要先看 ArrayList.class:
public Iterator<E> iterator() {
return new Itr();
}
/**
* An optimized version of AbstractList.Itr
*/
private class Itr implements Iterator<E> {
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 boolean hasNext() {
return cursor != size;
}
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
变量,相当于有一个更新操作,才通过了上边第一步的校验!
Java8的新个性
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
list.removeIf(item -> "b".equalsIgnoreCase(item) || "c".equalsIgnoreCase(item));
System.out.println("iterator:" + JSONObject.toJSONString(list));
}
总结
for-each循环不仅实用于遍历汇合和数组,而且能让你遍历任何实现Iterator接口的对象;最最要害的是它还没有性能损失。而对数组或汇合进行批改(增加删除操作),就要用迭代器循环。所以循环遍历所有数据的时候,能用它的时候还是抉择它吧。
发表回复