實現(xiàn)Java刪除一個集合的多個元素
問題
我需要從一個java的集合中,根據(jù)另一個集合的內(nèi)容,刪除第一個集合中不特定的元素。這看上去非常簡單,但卻遇到了問題。
這是我要寫的方法的頭部
private void screenBlackNameList(List<SharedBoardSmsWrapper> source, List<BlackNameListModel> blackNameList)
事情是這樣子的。source集合中保存了一些顯示用的數(shù)據(jù)元素。blackNameList集合中保存的是黑名單列表。我們需要根據(jù)黑名單表,把source集合中黑名單用戶的數(shù)據(jù)剔除掉。
這個問題的解決看上去非常簡單。
我首先使用for each 語句進行刪除。
for(SharedBoardSmsWrapper tmpSharedBoardSmsWrapper:source){
for(BlackNameListModel tmpBlackNameListModel:blackNameList){
if(tmpSharedBoardSmsWrapper.getSource().equals(tmpBlackNameListModel.getSource())){
source.remove(tmpSharedBoardSmsWrapper);
break;
}
}
}
非常簡單的問題!我暗笑,
測試…
令我意外的是,這段代碼居然拋出了異常
java.util.ConcurrentModificationException。
查看JDK6手冊
public class ConcurrentModificationException extends RuntimeException
當(dāng)方法檢測到對象的并發(fā)修改,但不允許這種修改時,拋出此異常。
例如,某個線程在 Collection 上進行迭代時,通常不允許另一個線性修改該 Collection。通常在這些情況下,迭代的結(jié)果是不確定的。如果檢測到這種行為,一些迭代器實現(xiàn)(包括 JRE 提供的所有通用 collection 實現(xiàn))可能選擇拋出此異常。執(zhí)行該操作的迭代器稱為 快速失敗 迭代器,因為迭代器很快就完全失敗,而不會冒著在將來某個時間任意發(fā)生不確定行為的風(fēng)險。
注意,此異常不會始終指出對象已經(jīng)由 不同 線程并發(fā)修改。如果單線程發(fā)出違反對象協(xié)定的方法調(diào)用序列,則該對象可能拋出此異常。例如,如果線程使用快速失敗迭代器在 collection 上迭代時直接修改該 collection,則迭代器將拋出此異常。
注意,迭代器的快速失敗行為無法得到保證,因為一般來說,不可能對是否出現(xiàn)不同步并發(fā)修改做出任何硬性保證。快速失敗操作會盡最大努力拋出 ConcurrentModificationException 。因此,為提高此類操作的正確性而編寫一個依賴于此異常的程序是錯誤的做法,正確做法是: ConcurrentModificationException 應(yīng)該僅用于檢測 bug。
Java中的For each實際上使用的是iterator進行處理的。而iterator是不允許集合在iterator使用期間刪除的。而我在for each時,從集合中刪除了一個元素,這導(dǎo)致了iterator拋出了 ConcurrentModificationException。
看來只有老老實實使用傳統(tǒng)的for循環(huán)了!
for(int i=0;i<source.size();i++){
SharedBoardSmsWrapper tmpSharedBoardSmsWrapper=source.get(i);
for(int j=0;j<blackNameList.size();j++){
BlackNameListModel tmpBlackNameListModel=blackNameList.get(j);
if(tmpSharedBoardSmsWrapper.getSource().equals(tmpBlackNameListModel.getSource())){
source.remove(tmpSharedBoardSmsWrapper);
break;
}
}
}
這下應(yīng)該沒問題了吧!信心滿滿地按下測試…
暈!怎么回事,數(shù)據(jù)怎么過濾得不對?
Debug跟蹤后發(fā)現(xiàn),原來,集合刪除元素時,集合的size會變小,連帶索引都會改變!
這可怎么辦?我不會被這樣一個小問題搞得沒轍了吧!
使用Iterator刪除集合中的元素
查看JDK手冊的Iterator接口,看到它還有一個remove方法。
remove
void remove()
從迭代器指向的 collection 中移除迭代器返回的最后一個元素(可選操作)。每次調(diào)用 next 只能調(diào)用一次此方法。如果進行迭代時用調(diào)用此方法之外的其他方式修改了該迭代器所指向的 collection,則迭代器的行為是不確定的。
拋出:
UnsupportedOperationException - 如果迭代器不支持 remove 操作。
IllegalStateException - 如果尚未調(diào)用 next 方法,或者在上一次調(diào)用 next 方法之后已經(jīng)調(diào)用了 remove 方法。
正確的最終代碼:
/**
*@paramsource
*@paramblackNameList
*/
privatevoid screenBlackNameList(List<SharedBoardSmsWrapper> source, List<BlackNameListModel> blackNameList){
Iterator<SharedBoardSmsWrapper> sourceIt=source.iterator();
while(sourceIt.hasNext()){
SharedBoardSmsWrapper tmpSharedBoardSmsWrapper=sourceIt.next();
Iterator<BlackNameListModel> blackNameListIt=blackNameList.iterator();
while(blackNameListIt.hasNext()){
BlackNameListModel tmpBlackNameListModel=blackNameListIt.next();
if(tmpSharedBoardSmsWrapper.getSource().equals(tmpBlackNameListModel.getSource())){
sourceIt.remove();
break;
}
}
}
}
注意,一次Iterator的next()方法,不能多次調(diào)用remove()方法。否則會拋出異常。
看來,刪除集合中的元素,最簡單的方法,就是使用Iterator的remove()方法了!
讓我們看看ArrayList類提供的Iterator是怎樣實現(xiàn)的。
privateclass Itr implements Iterator<E> {
/**
這是元素的索引,相當(dāng)于一個指針,或者游標(biāo),利用它來訪問List的數(shù)據(jù)元素。
*Indexofelementtobereturnedbysubsequentcalltonext.
*/
intcursor = 0;
/**
*Indexofelementreturnedbymostrecentcalltonextor
*previous. Resetto-1ifthiselementisdeletedbyacall
*toremove.
最新元素的索引。如果已經(jīng)刪除了該元素,就設(shè)為-1
*/
intlastRet = -1;
/**
外部類ArrayList的屬性:
protected transient int modCount = 0;
它用于觀察ArrayList是否同時在被其他線程修改,如果不一致,那么就會拋出同步異常。
*ThemodCountvaluethattheiteratorbelievesthatthebacking
*Listshouldhave. Ifthisexpectationisviolated,theiterator
*hasdetectedconcurrentmodification.
*/
intexpectedModCount = modCount;
//如果游標(biāo)沒有達到List的尺寸,那么就還有元素。
publicboolean hasNext() {
returncursor != size();
}
//返回當(dāng)前元素,然后游標(biāo)+1。最近索引 也= 返回的元素的索引。
public E next() {
checkForComodification();
try {
E next = get(cursor);
lastRet = cursor++;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
thrownew NoSuchElementException();
}
}
/*
刪除元素,就是刪除當(dāng)前元素,并且把游標(biāo)-1。因為,List會把后面的元素全部移前一位。
*/
publicvoid remove() {
if (lastRet == -1)
thrownew IllegalStateException();
checkForComodification();
try {
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
thrownew ConcurrentModificationException();
}
}
finalvoid checkForComodification() {
if (modCount != expectedModCount)
thrownew ConcurrentModificationException();
}
}
總結(jié)
可以看到,Iterator刪除了元素,并且把游標(biāo)重新置為正確的位子。只要沒有其他線程同時改變該集合,就不會有任何問題。以上就是本文的全部內(nèi)容了,希望對大家學(xué)習(xí)Java有所幫助。
相關(guān)文章
Java使用agent實現(xiàn)main方法之前的實例詳解
這篇文章主要介紹了Java使用agent實現(xiàn)main方法之前的實例詳解的相關(guān)資料,希望通過本文能幫助到大家,讓大家理解這部分內(nèi)容,需要的朋友可以參考下2017-10-10
java字符串日期類Date和Calendar相互轉(zhuǎn)化及相關(guān)常用方法
Java語言的Calendar(日歷),Date(日期),和DateFormat(日期格式)組成了Java標(biāo)準(zhǔn)的一個基本但是非常重要的部分,下面這篇文章主要給大家介紹了關(guān)于java字符串日期類Date和Calendar相互轉(zhuǎn)化及相關(guān)常用方法的相關(guān)資料,需要的朋友可以參考下2023-12-12
SpringBoot與Postman實現(xiàn)REST模擬請求的操作
這篇文章主要介紹了SpringBoot與Postman實現(xiàn)REST模擬請求的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-06-06
Java8 使用 stream().sorted()對List集合進行排序的操作
這篇文章主要介紹了Java8 使用 stream().sorted()對List集合進行排序的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-10-10
Java-Io-RandomAccessFile任意位置讀寫數(shù)據(jù)的操作小結(jié)
RandomAccessFile類支持隨機訪問方式,可以跳轉(zhuǎn)到文件的任意位置讀寫數(shù)據(jù),這個類在文件隨機讀取時有很大的優(yōu)勢,可利用多線程完成對一個大文件的讀寫,本文給大家介紹Java-Io-RandomAccessFile(任意位置讀寫數(shù)據(jù))的相關(guān)知識,需要的朋友可以參考下2022-05-05

