日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > java >内容正文

java

foreach去除重复元素java_Java foreach 中List移除元素抛出ConcurrentModificationException原因全解析...

發(fā)布時(shí)間:2025/4/16 java 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 foreach去除重复元素java_Java foreach 中List移除元素抛出ConcurrentModificationException原因全解析... 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

本文重點(diǎn)探討 foreach 循環(huán)中List 移除元素造成?java.util.ConcurrentModificationException 異常的原因。

先看《阿里巴巴 Java開發(fā)手冊(cè)》中的相關(guān)規(guī)定:

那么思考幾個(gè)問題:反例的運(yùn)行結(jié)果怎樣?

造成這種現(xiàn)象的根本原因是什么?

有沒有更優(yōu)雅地的移除元素姿勢(shì)?

本文將為你深度解讀該問題。

2.0 反例源代碼

public?class?ListExceptionDemo?{

public?static?void?main(String[]?args)?{

List?list?=?new?ArrayList<>();

list.add("1");

list.add("2");

for?(String?item?:?list)?{

if?("1".equals(item))?{

list.remove(item);

}

}

}

}

2.1 反例的運(yùn)行結(jié)果

當(dāng) if 的判斷條件是 “1”.equals(item) 時(shí),程序沒有拋出任何異常。if?("1".equals(item))?{

list.remove(item);

}

而當(dāng)判斷條件是 :"2".equals(item)時(shí),運(yùn)行會(huì)報(bào) java.util.ConcurrentModificationException。

2.2 原因分析

2.2.1 錯(cuò)誤提示

既然報(bào)錯(cuò),那么好辦,直接看錯(cuò)誤提示唄。Exception in thread "main" java.util.ConcurrentModificationException

at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)

at java.util.ArrayList$Itr.next(ArrayList.java:859)

at com.chujianyun.common.collection.list.ListExceptionDemo.main(ListExceptionDemo.java:13)

啥?ConcurrentModificationException? 并發(fā)修改異常? 一個(gè)線程哪來的并發(fā)呢?

對(duì)應(yīng)的時(shí)序圖

然后我們通過錯(cuò)誤提示看源碼:我們看到錯(cuò)誤的原因是執(zhí)行 ArrayList的 Itr.next 取下一個(gè)元素檢查 并發(fā)修改是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];

}

modCount 和 expectedModCount不一致導(dǎo)致的:final?void?checkForComodification()

{

if?(modCount?!=?expectedModCount)

throw?new?ConcurrentModificationException();

}

因此可以推測(cè)出發(fā)生異常的根本原因在于:取下一個(gè)元素時(shí),檢查 modCount,發(fā)現(xiàn)不一致。

2.2.2 代碼調(diào)試法

為了驗(yàn)證上面的推測(cè),大家可以在上述兩個(gè)關(guān)鍵函數(shù)上打斷點(diǎn),通過單步了解程序的運(yùn)行步驟。

我們通過調(diào)試可以“觀察到”,ArrayList中的?foreach 循環(huán)的語法糖最終迭代器Array$Itr 實(shí)現(xiàn)的。

通過斷點(diǎn)我們發(fā)現(xiàn),ArrayList 構(gòu)造內(nèi)部類 Itr 對(duì)象時(shí)?expectedModCount 的值為 ArrayList的 modCount。

運(yùn)行 next 函數(shù)時(shí)會(huì)檢查L(zhǎng)ist 中的 modCount 的值 和 構(gòu)造迭代器時(shí)“備份的” expectedModCount 是否相等。

通過調(diào)試我們還發(fā)現(xiàn):雖然原始 list 至于兩個(gè)元素,for each 循環(huán)執(zhí)行兩次后,滿足if 條件移除 值為“2”的元素之后, foreach 循環(huán)依然可以進(jìn)入,此時(shí)會(huì)再次通過 next 取出 list中的元素,又會(huì)執(zhí)行? checkForComodification函數(shù)檢查上述兩個(gè)值是否相等,此時(shí)不等,拋出異常。

那么這里有存在兩個(gè)問題:為什么 List 為 2? , next 卻執(zhí)行了 3 次呢?

如果不通過調(diào)試我們?cè)趺粗?foreach 語法糖的底層如何實(shí)現(xiàn)的呢?

帶著這兩個(gè)問題,我們繼續(xù)深入研究下去。

2.2.3? 源碼解析

我們查看? ArrayList$Itr 的 hasNext 函數(shù):private?class?Itr?implements?Iterator?{

int?cursor;???????//?index?of?next?element?to?return

int?lastRet?=?-1;?//?index?of?last?element?returned;?-1?if?no?such

int?expectedModCount?=?modCount;

Itr(){}

public?boolean?hasNext()?{

return?cursor?!=?size;

}

//?其他省略

}

發(fā)現(xiàn)ArrayList的迭代器判斷是否有下一個(gè)元素的標(biāo)準(zhǔn)是將下一個(gè)待返回的元素的索引和 size 比,不等表示還有下一個(gè)元素。

我們重新看源碼:public?static?void?main(String[]?args)?{

List?list?=?new?ArrayList<>();

list.add("1");

list.add("2");

for?(String?item?:?list)?{

if?("2".equals(item))?{

list.remove(item);

}

}

}

最初 List 中有兩個(gè)元素,expectedModCount ?值為2。

遍歷第一個(gè)時(shí)沒有走到if, 遍歷第二個(gè)元素時(shí)走到if ,通過 List.remove 函數(shù)移除了元素。public?boolean?remove(Object?o)?{

if?(o?==?null)?{

for?(int?index?=?0;?index?

if?(elementData[index]?==?null)?{

fastRemove(index);

return?true;

}

}?else?{

for?(int?index?=?0;?index?

if?(o.equals(elementData[index]))?{

fastRemove(index);

return?true;

}

}

return?false;

}

而remove會(huì)調(diào)用 fastRemove 函數(shù)實(shí)際移除掉元素,在此函數(shù)中會(huì)將 modCount+1,即 modCount的值為3。private?void?fastRemove(int?index)?{

modCount++;

int?numMoved?=?size?-?index?-?1;

if?(numMoved?>?0)

System.arraycopy(elementData,?index+1,?elementData,?index,

numMoved);

elementData[--size]?=?null;?//?clear?to?let?GC?do?its?work

}

因此在次進(jìn)入foreach 時(shí),expectedModCount ?值 和?modCount的值 不相等,因此認(rèn)為還有下一個(gè)元素。

但是調(diào)用迭代器的 next 函數(shù)時(shí)需檢查兩者是相等,發(fā)現(xiàn)不等,拋出ConcurrentModificationException異常。

當(dāng) if條件是? “1”.equals(item)時(shí)public?static?void?main(String[]?args)?{

List?list?=?new?ArrayList<>();

list.add("1");

list.add("2");

for?(String?item?:?list)?{

if?("1".equals(item))?{

list.remove(item);

}

}

}

循環(huán)取出第一個(gè)元素后直接通過list給移除掉了,再次進(jìn)入 foreach循環(huán)時(shí),通過 hashNext 判斷是否有下一個(gè)元素時(shí),由于 游標(biāo)==1(此時(shí)list的 size),因此判斷沒下一個(gè)元素。

也就是說此時(shí)循環(huán)只執(zhí)行了一次就結(jié)束了,沒有走到可以拋出ConcurrentModificationException異常的任何函數(shù)中,從而沒有任何錯(cuò)誤。

讀到這里對(duì)迭代器的理解是不是又深了一層呢?

看到這里可能還有些同學(xué)對(duì) foreach 究竟底層怎么實(shí)現(xiàn)的仍然一知半解,那么請(qǐng)看下一部分。

2.2.4 反匯編

話不多說,直接反匯編:public?class?com.chujianyun.common.collection.list.ListExceptionDemo?{

public?com.chujianyun.common.collection.list.ListExceptionDemo();

Code:

0:?aload_0

1:?invokespecial?#1??????????????????//?Method?java/lang/Object."":()V

4:?return

public?static?void?main(java.lang.String[]);

Code:

0:?new???????????#2??????????????????//?class?java/util/ArrayList

3:?dup

4:?invokespecial?#3??????????????????//?Method?java/util/ArrayList."":()V

7:?astore_1

8:?aload_1

9:?ldc???????????#4??????????????????//?String?1

11:?invokeinterface?#5,??2????????????//?InterfaceMethod?java/util/List.add:(Ljava/lang/Object;)Z

16:?pop

17:?aload_1

18:?ldc???????????#6??????????????????//?String?2

20:?invokeinterface?#5,??2????????????//?InterfaceMethod?java/util/List.add:(Ljava/lang/Object;)Z

25:?pop

26:?aload_1

27:?invokeinterface?#7,??1????????????//?InterfaceMethod?java/util/List.iterator:()Ljava/util/Iterator;

32:?astore_2

33:?aload_2

34:?invokeinterface?#8,??1????????????//?InterfaceMethod?java/util/Iterator.hasNext:()Z

39:?ifeq??????????72

42:?aload_2

43:?invokeinterface?#9,??1????????????//?InterfaceMethod?java/util/Iterator.next:()Ljava/lang/Object;

48:?checkcast?????#10?????????????????//?class?java/lang/String

51:?astore_3

52:?ldc???????????#6??????????????????//?String?2

54:?aload_3

55:?invokevirtual?#11?????????????????//?Method?java/lang/String.equals:(Ljava/lang/Object;)Z

58:?ifeq??????????69

61:?aload_1

62:?aload_3

63:?invokeinterface?#12,??2???????????//?InterfaceMethod?java/util/List.remove:(Ljava/lang/Object;)Z

68:?pop

69:?goto??????????33

72:?return

}

代碼偏移從 0 到 25 行實(shí)現(xiàn)下面這部分功能:List?list?=?new?ArrayList<>();

list.add("1");

list.add("2");

從 26行開始我們發(fā)現(xiàn)底層使用迭代器實(shí)現(xiàn),我們腦補(bǔ)后翻譯回 Java代碼大致如下:public?static?void?main(String[]?args)?{

List?list?=?new?ArrayList<>();

list.add("1");

list.add("2");

Iterator?iterator?=?list.iterator();

while?(iterator.hasNext())?{

String?item?=?iterator.next();

if?("2".equals(item))?{

//iterator.remove();

list.remove(item);

}

}

}

大家運(yùn)行“翻譯”后的代碼發(fā)信啊和原始代碼的報(bào)錯(cuò)內(nèi)容完全一致:Exception in thread "main" java.util.ConcurrentModificationException

at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)

at java.util.ArrayList$Itr.next(ArrayList.java:859)

at com.chujianyun.common.collection.list.ListException.main(ListException.java:16)

2.2.5 繼續(xù)深挖

1、為啥通過 iterator.remove() 移除元素就沒事呢?

我們看 java.util.ArrayList.Itr#remove 的源碼:public?void?remove()?{

if?(lastRet?

throw?new?IllegalStateException();

checkForComodification();

try?{

ArrayList.this.remove(lastRet);

cursor?=?lastRet;

lastRet?=?-1;

expectedModCount?=?modCount;

}?catch?(IndexOutOfBoundsException?ex)?{

throw?new?ConcurrentModificationException();

}

}

從這里我們看到,通過迭代器移除元素后, expectedModCount 會(huì)重新賦值為 modCount。

因此使用iterator.remove() 移除元素不報(bào)錯(cuò)的原因就找到了。

2、有沒有比手冊(cè)給出的代碼更優(yōu)雅的寫法?

我們打開其函數(shù)列表,觀察List 和其父類有沒有便捷地移除元素方式:

“驚奇”地發(fā)現(xiàn),Collection 接口提供了 removeIf 函數(shù)可以滿足此需求。

還等啥呢,替換下,發(fā)現(xiàn)代碼如此簡(jiǎn)潔:public?static?void?main(String[]?args)?{

List?list?=?new?ArrayList<>();

list.add("1");

list.add("2");????????//?一行代碼實(shí)現(xiàn)

list.removeIf("2"::equals);

}

自此是不是文章就該結(jié)束了呢?

NO..

removeIf 為啥能夠?qū)崿F(xiàn)移除元素的功能呢?

我們猜測(cè),底層應(yīng)該是遍歷然后對(duì)比元素然后移除,可能也是迭代器方式,我們看源碼:

java.util.Collection#removeIfdefault?boolean?removeIf(Predicate?super?E>?filter)?{

Objects.requireNonNull(filter);

boolean?removed?=?false;

final?Iterator?each?=?iterator();

while?(each.hasNext())?{

if?(filter.test(each.next()))?{

each.remove();

removed?=?true;

}

}

return?removed;

}

我們發(fā)現(xiàn)和我們想的比較一致。

本小節(jié)對(duì)《阿里巴巴 Java開發(fā)手冊(cè)》中 foreach 循環(huán) List 移除元素導(dǎo)致并發(fā)修改異常的問題,進(jìn)行了全面深入地剖析。

希望可以幫助大家,徹底搞懂這個(gè)問題。

另外也提供了研究類似問題的一般思路,即代碼調(diào)試、讀源碼、反匯編等。

通過這個(gè)問題,希望大家遇到問題時(shí),能夠養(yǎng)成深挖的精神,通過問題帶動(dòng)知識(shí)的理解,知其所以然。

最后提醒大家,不要看書記結(jié)論,容易忘,記住不會(huì)用,要多思考原因,才能理解更深刻。

“盡信書不如無書”,不要認(rèn)為作者寫的都是對(duì)的,都是最好的,要有自己的思考。

想了解更多《手冊(cè)》詳解的更多內(nèi)容,想學(xué)習(xí)更多開發(fā)和避坑技巧等,請(qǐng)關(guān)注《阿里巴巴Java 開發(fā)手冊(cè)》詳解專欄。

總結(jié)

以上是生活随笔為你收集整理的foreach去除重复元素java_Java foreach 中List移除元素抛出ConcurrentModificationException原因全解析...的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。