文章目錄
-
- 認識foreach
- 了解使用限制,防止錯誤使用
- 錯誤使用
- 正确使用
- 總結
并發修改異常是指:
ConcurrentModificationException
。
認識foreach
foreach循環是JDK1.5開始引入的,這種方式周遊集合或數組,代碼更加簡潔。
foreach循環本質上來說可以周遊任何實作了Iterable接口的對象。
foreach本質上不過是編譯器提供的“文法糖”包裝。編譯器在遇到
for(Type item : arrayOrList) { }
代碼時,會進行代碼的轉譯。
如果是數組,會轉成數組的周遊方式:
for(int i=0;i<array.length;i++) {
Type item = array[i];
/* body-of-loop */
}
如果是周遊集合類型,則要求被周遊的集合類型實作
java.lang.Iterable
接口,在iterator()方法中傳回一個Iterator疊代器。
//Iterable.java
public interface Iterable<T> {
Iterator<T> iterator();
}
//Iterator.java
public interface Iterator<E> {
boolean hasNext();
E next();
default void remove() {
throw new UnsupportedOperationException("remove");
}
}
相應的foreach代碼會被編譯器轉換成Iterator的疊代方式:
for(Iterator<Type> iter = list.iterator(); iter.hasNext(); ) {
Type item = iter.next();
/* body-of-loop */
}
了解使用限制,防止錯誤使用
foreach并不是萬能的。在某些場景下不能使用。
- foreach在周遊過程中不能修改集合中元素的值。不過,如果周遊的是數組,則不受此限制。
- foreach在周遊過程中不能往集合中增加或删除元素,否則ConcurrentModificationException異常。即使在個别特殊情況下沒有抛出這個異常,那也是因為巧合(下文會有說明)。
- 周遊過程中,集合或數組中同時隻有一個元素可見,即隻有“目前周遊到的元素”可見,而前一個或後一個元素是不可見的。
- 隻能從前往後正向周遊,不能反向周遊。
錯誤使用
關于第2點,嘗試在foreach周遊過程中,添加或删除元素:
ArrayList<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
final String toRemove = "2";
final String toAdd = "1000";
for (String item : list) {
//item = "100"; //這句執行無效,僅僅改變item的指向,不會改變list中的元素
if (toRemove.equals(item)) {
list.remove(item); //僅當toRemove為"3"時,沒有報異常。這是删除倒數第二個元素情況下的“巧合”。
//list.add(toAdd); // 報ConcurrentModificationException
}
}
原因分析:
- ArrayList内部有一個成員變量
,記錄list内部元素改變的次數。modCount
- 通過
傳回一個新的疊代器對象的時候,iter内部會用iter=list.iterator()
成員變量記錄下當時的expectedModCount
的值。在整個循環的周遊過程中,不管是modCount
還是iter.next()
方法都會檢查原ArrayList的iter.remove()
值是否與iter内部記錄的modCount
值一緻,一旦不一緻就會抛expectedModCount
。是以,異常是在增減集合元素後,下一輪循環的ConcurrentModificationException
方法中抛出的。iter.next()
- 相關源代碼
public boolean hasNext() {
return cursor != size;
}
public E next() {
// 此處抛出異常
checkForComodification();
// 省略其他代碼...
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
-
當toRemove為"3"時,沒有報異常,這是為什麼呢?
因為,這種情況下,一旦remove之後,原ArrayList的size會減少1,下一輪通過iter.hasNext()(hasNext隻是傳回疊代器内部的疊代位置cursor是否已達到被疊代容器的size,本身不會抛異常)判斷是否還有元素時,發現沒有了,直接傳回false,進而不會調用到
方法。當然也就不會有從這個方法中抛出的異常啦。iter.next()
正确使用
那麼問題來了,如果要在周遊集合的過程中需要删除或添加元素該怎麼辦?
- 普通for循環
for(int i=0;i<list.size();i++) { String item = list.get(i); if ("3".equals(item)) { list.remove(i);//為了效率,這裡最好不要用list.remove(item) } }
- Iterator疊代器方式周遊,通過疊代器的remove方法進行删除。
Iterator<String> iter = list.iterator(); while(iter.hasNext()) { String item = iter.next(); if ("4".equals(item)) { iter.remove(); } }
總結
在 subList 場景中,高度注意對父集合元素的增加或删除,均會導緻子清單的周遊、
增加、删除産生 ConcurrentModificationException 異常。——阿裡巴巴Java開發規範(嵩山版)
好了,以上就是全部内容。