為什么不能在foreach中刪除元素

文章目錄

  • 快速失敗機制(fail-fast)
  • for-each刪除元素為什么報錯
    • 原因分析
    • 邏輯分析
  • 如何正確的刪除元素
    • remove 后 break
    • for 循環
    • 使用 Iterator
  • 總結

快速失敗機制(fail-fast)

In systems design, a fail-fast system is one which immediately reports at its interface any condition that is likely to indicate a failure. Fail-fast systems are usually designed to stop normal operation rather than attempt to continue a possibly flawed process. Such designs often check the system’s state at several points in an operation, so any failures can be detected early. The responsibility of a fail-fast module is detecting errors, then letting the next-highest level of the system handle them.

這是快速失敗機制的英文解釋。翻譯過來就是:系統設計中,“fail-fast”指的是一種策略,系統或模塊被設計成在出現錯誤或失敗時立即檢測并報告。這種方法旨在通過停止正常操作而不是繼續可能存在缺陷的過程來最小化失敗的影響。fail-fast系統通常在操作的多個點檢查系統狀態,以便及早發現任何失敗。fail-fast模塊的責任是檢測錯誤,然后讓系統的更高級別處理它們。

這段話的大致意思就是,fail-fast 是一種通用的系統設計思想,一旦檢測到可能會發生錯誤,就立馬拋出異常,程序將不再往下執行

很多時候,我們會把 fail-fast 歸類為 Java 集合框架的一種錯誤檢測機制,但其實 fail-fast 并不是 Java 集合框架特有的機制

for-each刪除元素為什么報錯

下面這段代碼:

List<String> list = new ArrayList<>();list.add("1");list.add("2");list.add("3");for (String str : list) {if ("1".equals(str)) {list.remove(str);}}System.out.println(list);

在執行完之后就會報錯
在這里插入圖片描述
看一下報錯的原因是在checkForComodification這里報的錯。下面是具體的代碼

final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();}

也就是說,remove 的時候觸發執行了 checkForComodification 方法,該方法對 modCount 和 expectedModCount 進行了比較,發現兩者不等,就拋出了 ConcurrentModificationException 異常。

原因分析

為什么會執行checkForComodification 方法呢?是因為for-each的底層是迭代器Iterator配合while來實現的

List<String> list = new ArrayList();
list.add("1");
list.add("2");
list.add("3");
Iterator var2 = list.iterator();while(var2.hasNext()) {String str = (String)var2.next();if ("1".equals(str)) {list.remove(str);}
}System.out.println(list);

看一下list的迭代器,點進iterator這個方法,發現它實現了Iterator接口

在這里插入圖片描述
再去看一下 Itr 這個類。

    private class Itr implements Iterator<E> {int cursor;       // index of next element to returnint lastRet = -1; // index of last element returned; -1 if no suchint expectedModCount = modCount;// prevent creating a synthetic constructorItr() {}public boolean hasNext() {return cursor != size;}@SuppressWarnings("unchecked")public E next() {checkForComodification();int i = cursor;if (i >= size)throw new NoSuchElementException();Object[] elementData = ArrayList.this.elementData;if (i >= elementData.length)throw new ConcurrentModificationException();cursor = i + 1;return (E) elementData[lastRet = i];}public void remove() {if (lastRet < 0)throw new IllegalStateException();checkForComodification();try {ArrayList.this.remove(lastRet);cursor = lastRet;lastRet = -1;expectedModCount = modCount;} catch (IndexOutOfBoundsException ex) {throw new ConcurrentModificationException();}}@Overridepublic void forEachRemaining(Consumer<? super E> action) {Objects.requireNonNull(action);final int size = ArrayList.this.size;int i = cursor;if (i < size) {final Object[] es = elementData;if (i >= es.length)throw new ConcurrentModificationException();for (; i < size && modCount == expectedModCount; i++)action.accept(elementAt(es, i));// update once at end to reduce heap write trafficcursor = i;lastRet = i - 1;checkForComodification();}}final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();}}

也就是說 new Itr() 的時候 expectedModCount 被賦值為 modCount,而 modCount 是 ArrayList 中的一個計數器,用于記錄 ArrayList 對象被修改的次數。ArrayList 的修改操作包括添加、刪除、設置元素值等。每次對 ArrayList 進行修改操作時,modCount 的值會自增 1。

在迭代 ArrayList 時,如果迭代過程中發現 modCount 的值與迭代器的 expectedModCount 不一致,則說明 ArrayList 已被修改過,此時會拋出 ConcurrentModificationException 異常。這種機制可以保證迭代器在遍歷 ArrayList 時,不會遺漏或重復元素,同時也可以在多線程環境下檢測到并發修改問題。

邏輯分析

List<String> list = new ArrayList<>();list.add("1");list.add("2");list.add("3");for (String str : list) {if ("1".equals(str)) {list.remove(str);}}System.out.println(list);

由于 list 此前執行了 3 次 add 方法。

  • add 方法調用 ensureCapacityInternal 方法
  • ensureCapacityInternal 方法調用ensureExplicitCapacity 方法
  • ensureExplicitCapacity 方法中會執行 modCount++

所以 modCount 的值在經過三次 add 后為 3,于是 new Itr() 后 expectedModCount 的值也為 3(回到前面去看一下 Itr 的源碼)。

接著來執行 for-each 的循環遍歷。

執行第一次循環時,發現“沉默王二”等于 str,于是執行 list.remove(str)。

  • remove 方法調用 fastRemove 方法
  • fastRemove 方法中會執行 modCount++

modCount 的值變成了 4。

第二次遍歷時,會執行 Itr 的 next 方法(String str = (String) var3.next();),next 方法就會調用 checkForComodification 方法。

此時 expectedModCount 為 3,modCount 為 4,就只好拋出 ConcurrentModificationException 異常了。

如何正確的刪除元素

remove 后 break

List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");for (String str : list) {if (1".equals(str)) {list.remove(str);break;}
}

break 后循環就不再遍歷了,意味著 Iterator 的 next 方法不再執行了,也就意味著 checkForComodification 方法不再執行了,所以異常也就不會拋出了。

但是呢,當 List 中有重復元素要刪除的時候,break 就不合適了。

for 循環

List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
for (int i = 0; i < list.size(); i++) {String str = list.get(i);if ("1".equals(str)) {list.remove(str);}
}

for 循環雖然可以避開 fail-fast 保護機制,也就說 remove 元素后不再拋出異常;但是呢,這段程序在原則上是有問題的。為什么呢?

第一次循環的時候,i 為 0,list.size() 為 3,當執行完 remove 方法后,i 為 1,list.size() 卻變成了 2,因為 list 的大小在 remove 后發生了變化,也就意味著“2”這個元素被跳過了。能明白嗎?

remove 之前 list.get(1) 為“2”;但 remove 之后 list.get(1) 變成了“3”,而 list.get(0) 變成了“2”

使用 Iterator

List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");Iterator<String> itr = list.iterator();while (itr.hasNext()) {String str = itr.next();if ("1".equals(str)) {itr.remove();}
}

為什么使用 Iterator 的 remove 方法就可以避開 fail-fast 保護機制呢?看一下 remove 的源碼就明白了。

public void remove() {if (lastRet < 0) // 如果沒有上一個返回元素的索引,則拋出異常throw new IllegalStateException();checkForComodification(); // 檢查 ArrayList 是否被修改過try {ArrayList.this.remove(lastRet); // 刪除上一個返回元素cursor = lastRet; // 更新下一個元素的索引lastRet = -1; // 清空上一個返回元素的索引expectedModCount = modCount; // 更新 ArrayList 的修改次數} catch (IndexOutOfBoundsException ex) {throw new ConcurrentModificationException(); // 拋出異常}
}

刪除完會執行 expectedModCount = modCount,保證了 expectedModCount 與 modCount 的同步

總結

在使用 foreach 循環(或稱 for-each 循環)遍歷集合時,通常不能直接刪除集合中的元素,原因如下:

Concurrent Modification Exception:
當使用 foreach 循環遍歷集合時,集合的結構不能被修改(例如添加或刪除元素),否則會導致 ConcurrentModificationException 異常。這是因為 foreach 循環在背后使用迭代器來遍歷集合,而迭代器在遍歷時會維護一個 expected modCount(修改計數器),如果在遍歷過程中修改了集合的結構,迭代器會檢測到并拋出異常。

Invalidation of Iterator:
刪除元素后,集合的結構發生變化,這可能會使當前的迭代器失效。如果集合的結構發生了變化,迭代器可能無法正確遍歷集合的剩余部分或者導致未定義行為。

Potential Logical Errors:
直接在 foreach 循環內刪除元素可能會導致邏輯錯誤。例如,如果不正確地更新迭代器或集合的大小,可能會導致遍歷的元素不完整或錯誤。

為了安全地從集合中刪除元素,應該使用迭代器的 remove() 方法。迭代器的 remove() 方法允許在遍歷時安全地刪除當前元素,同時更新集合的結構和迭代器的狀態,避免了上述問題。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/40210.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/40210.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/40210.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

合肥高新區建設世界領先科技園區政策(商務部分)申報獎勵補貼和條件材料、時間指南

一、合肥高新區建設世界領先科技園區政策&#xff08;商務部分&#xff09;申報主體 &#xff08;更多政策可以查看小編主頁方式&#xff09; 工商、稅務、統計關系均在合肥高新區&#xff0c;并在高新區持續經營。申請項目在高新區內實施、且符合政策要求的具有獨立法人資格…

網絡基礎:EIGRP

EIGRP&#xff08;Enhanced Interior Gateway Routing Protocol&#xff09;是由思科開發的一種高級距離矢量路由協議&#xff0c;結合了距離矢量和鏈路狀態路由協議的優點&#xff1b;EIGRP具有快速收斂、高效帶寬利用、負載均衡等特點&#xff0c;適用于各種規模的網絡。EIGR…

python sklearn機械學習-數據預處理

&#x1f308;所屬專欄&#xff1a;【機械學習】?作者主頁&#xff1a; Mr.Zwq??個人簡介&#xff1a;一個正在努力學技術的Python領域創作者&#xff0c;擅長爬蟲&#xff0c;逆向&#xff0c;全棧方向&#xff0c;專注基礎和實戰分享&#xff0c;歡迎咨詢&#xff01; 您…

【設計模式】策略模式(定義 | 特點 | Demo入門講解)

文章目錄 定義策略模式的結構 QuickStart | DemoStep1 | 策略接口Step2 | 策略實現Step3 | 上下文服務類Step4 | 客戶端 策略模式的特點優點缺點 定義 策略模式Strategy是一種行為模式&#xff0c;它能定義一系列算法&#xff0c;并將每種算法分別放入到獨立的類中&#xff0c…

書籍表達式得到期望結果的組成種數

題目 給定一個只由0(假)、1(真)、&(邏輯與)、|(邏輯或)和^(異或)五種組成的字符串express&#xff0c;再給定一個布爾值desired。返回express能有多少種組合方式。可以達到desired的結果。 舉例 express“1^0|0|1”,desiredfalse. 只有1^((0|0)|1)和1^(0|(0|1))的組合可…

負載均衡類型和算法解析

假如你正在設計和開發一個分布式服務系統&#xff0c;系統中存在一批能夠獨立運行的服務&#xff0c;而在部署上也采用了集群模式以防止出現單點故障。所謂集群&#xff0c;就是指將多個服務實例集中在一起&#xff0c;對外提供同一業務功能&#xff0c;也就是任意請求都可以由…

吉利銀河L6 AQS空氣質量監控系統

結論 頂配才有AQS 開啟空調且auto模式 則默認開啟AQS 無法關閉AQS AQS的作用 銀河L6 AQS觸發 和 圖標 AQS官方配置參數 官方文檔 吉利用戶手冊

開源即正義,3D軟件Blender設計指南

在當今數字化時代&#xff0c;開源軟件的崛起不僅代表著技術的發展&#xff0c;更象征著一種信息自由和技術民主的理念。其本質是集眾人之智&#xff0c;共同去完善一個軟件&#xff0c;最終使雙方互惠共贏。具體來說&#xff0c;開源的價值&#xff0c;在于打破資源壟斷&#…

Spring的事務管理、AOP實現底層

目錄 spring的事務管理是如何實現的&#xff1f; Spring的AOP的底層實現原理 spring的事務管理是如何實現的&#xff1f; 首先&#xff0c;spring的事務是由aop來實現的&#xff0c;首先要生成具體的代理對象&#xff0c;然后按照aop的整套流程來執行具體的操作邏輯&#xf…

NLP - 基于bert預訓練模型的文本多分類示例

項目說明 項目名稱 基于DistilBERT的標題多分類任務 項目概述 本項目旨在使用DistilBERT模型對給定的標題文本進行多分類任務。項目包括從數據處理、模型訓練、模型評估到最終的API部署。該項目采用模塊化設計&#xff0c;以便于理解和維護。 項目結構 . ├── bert_dat…

蘋果AI的國產大模型之爭,沒有懸念

文 | 智能相對論 作者 | 陳泊丞 蘋果終于公布了最新的AI進程。 一個月前&#xff0c;正如此前預期的那樣&#xff0c;人工智能是今年 WWDC 發布會的焦點。全程105分鐘的主題演講&#xff0c;就有40多分鐘用于介紹蘋果的AI成果。 蘋果似乎還有意玩了一把“諧音梗”&#xff…

用機器改變人類方向

1800 世紀初&#xff0c;美國迎來了工業革命&#xff0c;這是一個由技術進步推動的變革時代。新機器和制造技術的引入重塑了經濟格局&#xff0c;提高了生產效率&#xff0c;同時減少了某些領域對手工勞動的需求。因此&#xff0c;這種轉變導致了失業。 如今&#xff0c;我們看…

實現點擊按鈕導出頁面pdf

在Vue 3 Vite項目中&#xff0c;你可以使用html2canvas和jspdf庫來實現將頁面某部分導出為PDF文檔的功能。以下是一個簡單的實現方式&#xff1a; 1.安裝html2canvas和jspdf&#xff1a; pnpm install html2canvas jspdf 2.在Vue組件中使用這些庫來實現導出功能&#xff1a;…

統計信號處理基礎 習題解答11-11

題目 考慮矢量MAP估計量 證明這個估計量對于代價函數 使貝葉斯風險最小。其中&#xff1a;, &#xff0c;且. 解答 貝葉斯風險函數&#xff1a; 基于概率密度的非負特性&#xff0c;上述對積分要求最小&#xff0c;那就需要內層積分達到最小。令內層積分為&#xff1a; 上述積…

蘋果Mac電腦能玩什么游戲 Mac怎么運行Windows游戲

相對于Windows平臺來說&#xff0c;Mac電腦可玩的游戲較少。雖然蘋果設備的性能足以支持各種大型游戲&#xff0c;但由于系統以及蘋果配套服務的限制&#xff0c;很多游戲無法在Mac系統中運行。不過&#xff0c;借助虛擬機軟件&#xff0c;Mac電腦可以突破系統限制玩更多的游戲…

react中jsx的語法規則

1.react核心庫react.development.js 2.react_dom庫&#xff0c;用于支持react操作dom&#xff08;react-dom.development.js&#xff09; 3.引入bable&#xff0c;解析jsx語法的庫&#xff0c;用于將jsx轉換為js(babel.min.js) 上述三個庫是寫基礎react的基本庫 下面我將用…

光照老化試驗箱在化工產品暴曬測試中的應用

概述 光照老化試驗箱是一種模擬自然光照條件下材料老化情況的實驗設備&#xff0c;廣泛應用于化工、建材、電子、汽車等行業中對材料的耐候性、耐光性能等進行測試。通過模擬日光中的紫外線和溫度等環境因素&#xff0c;加速材料老化過程&#xff0c;以此評估材料在長期使用中…

2024阿里云大模型自定義插件(如何調用自定義接口)

1&#xff0c;自定義插件入口 2&#xff0c;插件定義&#xff1a;描述插件的參數 2.1&#xff0c;注意事項&#xff1a; 2.1.1&#xff0c;只支持json格式的參數&#xff1b;只支持application/JSON&#xff1b;如下圖&#xff1a; 2.1.2&#xff0c;需要把接口描述進行修改&a…

03:Spring MVC

文章目錄 一&#xff1a;Spring MVC簡介1&#xff1a;說說自己對于Spring MVC的了解&#xff1f;1.1&#xff1a;流程說明&#xff1a; 一&#xff1a;Spring MVC簡介 Spring MVC就是一個MVC框架&#xff0c;Spring MVC annotation式的開發比Struts2方便&#xff0c;可以直接代…

LeetCode 算法:二叉搜索樹中第K小的元素 c++

原題鏈接&#x1f517;&#xff1a;二叉搜索樹中第K小的元素 難度&#xff1a;中等???? 題目 給定一個二叉搜索樹的根節點 root &#xff0c;和一個整數 k &#xff0c;請你設計一個算法查找其中第 k 小的元素&#xff08;從1開始計數&#xff09;。 示例 1&#xff1a;…