需求:從 source 列表中移除 blackNameList 包含的元素。初版代碼:
for (SharedBoardSmsWrapper item : source) {
for (BlackNameListModel black : blackNameList) {
if (item.getSource().equals(black.getSource())) {
source.remove(item); // 拋出 ConcurrentModificationException
break;
}
}
}
for-each 編譯後使用 Iterator 遍歷。Iterator 內部維護 expectedModCount,檢測到集合結構變更(如直接 list.remove())即拋出 ConcurrentModificationException(fail-fast 機制)。for (int i = 0; i < source.size(); i++) {
// ...
source.remove(item); // 索引錯位,導致漏刪或越界
}
原因:刪除元素後後續元素前移,索引 i 再自增會跳過下一個元素。
Iterator<SharedBoardSmsWrapper> it = source.iterator();
while (it.hasNext()) {
SharedBoardSmsWrapper item = it.next();
for (BlackNameListModel black : blackNameList) {
if (item.getSource().equals(black.getSource())) {
it.remove(); // 唯一合法的遍歷中刪除方式
break;
}
}
}
ArrayList.Itr 內部實現:
public void remove() {
if (lastRet == -1) throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.remove(lastRet); // 刪除最後訪問的元素
if (lastRet < cursor) cursor--; // 修正游標
lastRet = -1;
expectedModCount = modCount; // 同步修改計數
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
關鍵點:
next() 返回的元素(lastRet)。expectedModCount 同步更新,避免 fail-fast 觸發。next() 只能調用一次 remove(),否則 IllegalStateException。// 1. removeIf (單條件)
source.removeIf(item -> blackNameList.stream()
.anyMatch(black -> item.getSource().equals(black.getSource())));
// 2. Stream 過濾生成新列表 (不修改原列表)
List<SharedBoardSmsWrapper> filtered = source.stream()
.filter(item -> blackNameList.stream()
.noneMatch(black -> item.getSource().equals(black.getSource())))
.collect(Collectors.toList());
| 場景 | 推薦方式 | 理由 |
|---|---|---|
| 單列表原地刪除、邏輯簡單 | Iterator.remove() / removeIf() | O(1) 空間、代碼清晰 |
| 需保留原列表、或邏輯複雜 | Stream 過濾新建列表 | 無副作用、易並行、函數式風格 |
| 雙重循環嵌套刪除 | 外層 Iterator + 內層遍歷 | 避免 ConcurrentModificationException |
遍歷時要刪元素,Iterator.remove 最靠譜;for-each 別動底層集,普通 for 索引易亂;Java 8 有 removeIf,Stream 過濾更優雅。