有時候我們需要對ArrayList進行遍歷,然后根據條件刪除元素,就像下面這樣:
public static void main(String[] args) {
ArrayList<String> list=new ArrayList<>();
list.add("aa");
list.add("bb");
list.add("cc");
for (String s : list) {
if ("aa".equals(s)) {
list.remove("aa");
}
}
}
然后會報如下的錯誤:
我們看一下生成的class文件的反編譯的結果:
public static void main(String[] args) {
ArrayList<String> list = new ArrayList();
list.add("aa");
list.add("bb");
list.add("cc");
Iterator var2 = list.iterator();
while(var2.hasNext()) {
String s = (String)var2.next();
if ("aa".equals(s)) {
list.remove("aa");
}
}
}
可以看到,這里遍歷用的是ArrayList實現的迭代器Iterator的hasNext()、next()方法,但是刪除用的卻是ArrayList的remove(Object o)方法。這樣迭代器無法得知ArrayList中元素的變化,比如ArrayList中已經刪了一個元素,后面的元素都向前移動一個位置,原本Iterator位置上的元素被刪除了並且被后面的元素替代,而Iterator不知道,下一次迭代的時候就會以為這個元素已經被遍歷過了而直接跳過。
ArrayList中會對這樣的行為進行監控:
原來ModCount和ExpectedModCount是相等的,而ArrayList.remove(Object o)每被執行一次ModCount都會加1:
Iterator中會驗證ModCount和ExpectedModCount是否相等:
如果不相等就會拋出ConcurrentModificationException異常
正確的做法:
看一下這里的iterator.remove()做了什么事:
其實就是在調用list.remove(Object o)的時候加上一句"expectedModCount = modCount;",這樣在下一次迭代的時候他們的值就相等了。
補充:
可以先用javac將java編譯成.class文件(javac [-encoding utf-8] javaName.java),然后在開發工具中打開,可以查看.class文件的反編譯內容;或者直接在開發工具中查看class文件(反編譯后的文件)。用以查看一些“不可見”方法的具體實現:
for (String s : list) {
if ("aa".equals(s)) {
list.remove("aa");
}
}
Iterator var2 = list.iterator();
while(var2.hasNext()) {
String s = (String)var2.next();
if ("aa".equals(s)) {
list.remove("aa");
}
}
注:如果用jdk自帶的javap指令,即javap className,可能看到的源代碼的原貌只剩下了方法名