如何處理集合型態物件的 ConcurrentModificationException
不知道大家有沒有遇到過,開發功能中若使用集合型態物件(無論是宣告為區域變數或屬性),常利用迴圈(while+Iterator或forEach)逐個取得集合元素。若此時需要在迴圈中移除(最後一個以外的)某個元素,並進入下一個元素(next( )方法)時,會發生ConcurrentModificationException而造成異常結束。
下列程式,來示範這個情況。這裡定義的MySet類別中有定義一個private TreeSet型態的集合屬性,並提供了getter以符合屬性封裝的設計原則。在建構式中也對dateSet加入了「從今天往前最近的星期日開始」一週的日期(LocalDate)物件,程式如下:
import java.time.LocalDate;
import java.util.Set;
import java.util.TreeSet;
public class MySet {
private Set<LocalDate> dateSet = new TreeSet<>();
public MySet() {
LocalDate today = LocalDate.now();
int i= -today.getDayOfWeek().getValue();
for (int j=i;j<(7+i);j++) { //加入從今天往前最近星期日起一週日期
dateSet.add(today.plusDays(j));
}
}
/**
* @param keyDate 移除dateSet中指定的日期物件
*/
public void remove(LocalDate keyDate) {
dateSet.remove(keyDate);
}
/**
* @return 直接回傳dateSet屬性參考(這是不正確的設計)
*/
public Set<LocalDate> getDateSet() {
return dateSet; //直接回傳該set物件參考
}
@Override
public String toString() {
return "MySet [dateSet=" + dateSet + "]";
}
}
在TestMySet的main方法中示範的程式會發生集合型態物件特有的例外:
java.util.ConcurrentModificationException
程式如下:
package patty.mod15.test;
import java.time.LocalDate;
import java.util.Iterator;
import patty.mod15.entity.MySet;
public class TestMySet {
public static void main(String[] args) {
MySet mySet = new MySet();
System.out.println(mySet); //列出mySet的內容
LocalDate today = LocalDate.now();
Iterator<LocalDate> mySetIterator=mySet.getDateSet().iterator();
while(mySetIterator.hasNext()) {
LocalDate theDate = mySetIterator.next(); //這裡是第15行
if (today.isAfter(theDate)) { //將小於今天的日期移除
mySet.remove(theDate);
}
}
System.out.println(mySet); //列出mySet的內容
}
}
執行結果如下圖,在main程式的第15行mySetIterator.next()方法發生下列錯誤:
而發生錯誤的主因就是:在用Iterator指位器逐個取得集合元素時,直接將元素從集合中移除,會造成指位器往下的順序錯亂。就好比把一串珠珠從中間剪斷一個,後面的珠串也會就斷掉而無法繼續處理。
因此正確簡單的修改方式,是取得原來集合物件的複本,也就是建立新的集合元件來複製原來的集合內容,讓複本集合先抓住每個元素,藉由複本的Iterator來取得元素,再到正本(來源集合)remove想要移除的元素,就不會造成Iterator指位器的順序錯亂了。
所以是在MySet程式中修改dateSet屬性的getter內容,如下圖第32行的程式:
而TestMySet類別main方法中看來錯誤的第15行程式,則完全不用修改,如下圖:
直接執行就會有正確的結果了,如下圖:
0 意見:
張貼留言