激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術(shù)|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務(wù)器之家 - 編程語言 - Java教程 - Java中ArrayList在foreach里remove的問題詳析

Java中ArrayList在foreach里remove的問題詳析

2021-05-30 16:07蒼楓露雨 Java教程

這篇文章主要給大家介紹了關(guān)于Java中ArrayList在foreach里remove問題的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面來一起看看吧

前言

arraylist就是傳說中的動態(tài)數(shù)組,用msdn中的說法,就是array的復雜版本,它提供了如下一些好處:

  • 動態(tài)的增加和減少元素
  • 實現(xiàn)了icollection和ilist接口
  • 靈活的設(shè)置數(shù)組的大小

都說arraylist在用foreach循環(huán)的時候,不能add元素,也不能remove元素,可能會拋異常,那我們就來分析一下它具體的實現(xiàn)。我目前的環(huán)境是java8。

有下面一段代碼:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class testforeachlist extends basetests {
 
 @test
 public void testforeach() {
 list<string> list = new arraylist<>();
 list.add("1");
 list.add("2");
 list.add("3");
 
 for (string s : list) {
 }
 }
 
}

代碼很簡單,一個arraylist添加3個元素,foreach循環(huán)一下,啥都不干。那么foreach到底是怎么實現(xiàn)的呢,暴力的方法看一下,編譯改類,用 javap -c testforeachlist查看class文件的字節(jié)碼,如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
javap -c testforeachlist
warning: binary file testforeachlist contains collection.list.testforeachlist
compiled from "testforeachlist.java"
public class collection.list.testforeachlist extends com.ferret.basetests {
 public collection.list.testforeachlist();
 code:
 0: aload_0
 1: invokespecial #1  // method com/ferret/basetests."<init>":()v
 4: return
 
 public void testforeach();
 code:
 0: new #2  // class java/util/arraylist
 3: dup
 4: invokespecial #3  // method java/util/arraylist."<init>":()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: ldc #7  // string 3
 29: invokeinterface #5, 2 // interfacemethod java/util/list.add:(ljava/lang/object;)z
 34: pop
 35: aload_1
 36: invokeinterface #8, 1 // interfacemethod java/util/list.iterator:()ljava/util/iterator;
 41: astore_2
 42: aload_2
 43: invokeinterface #9, 1 // interfacemethod java/util/iterator.hasnext:()z
 48: ifeq 64
 51: aload_2
 52: invokeinterface #10, 1 // interfacemethod java/util/iterator.next:()ljava/lang/object;
 57: checkcast #11  // class java/lang/string
 60: astore_3
 61: goto 42
 64: return
}

可以勉強讀,大約是調(diào)用了list.iterator,然后根據(jù)iterator的hasnext方法返回結(jié)果判斷是否有下一個,根據(jù)next方法取到下一個元素。

但是是總歸是體驗不好,我們是現(xiàn)代人,所以用一些現(xiàn)代化的手段,直接用idea打開該class文件自動反編譯,得到如下內(nèi)容:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class testforeachlist extends basetests {
 public testforeachlist() {
 }
 
 @test
 public void testforeach() {
 list<string> list = new arraylist();
 list.add("1");
 list.add("2");
 list.add("3");
 
 string var3;
 for(iterator var2 = list.iterator(); var2.hasnext(); var3 = (string)var2.next()) {
 ;
 }
 
 }
}

體驗好多了,再對比上面的字節(jié)碼文件,沒錯

?
1
2
3
for(iterator var2 = list.iterator(); var2.hasnext(); var3 = (string)var2.next()) {
 ;
 }

這就是脫掉語法糖外殼的foreach的真正實現(xiàn)。

接下來我們看看這三個方法具體都是怎么實現(xiàn)的:

iterator

arraylist的iterator實現(xiàn)如下:

?
1
2
3
4
5
6
7
8
9
10
public iterator<e> iterator() {
 return new itr();
}
 
private class itr implements iterator<e> {
 int cursor; // index of next element to return
 int lastret = -1; // index of last element returned; -1 if no such
 int expectedmodcount = modcount;
 //省略部分實現(xiàn)
}

itr是arraylist中的內(nèi)部類,所以list.iterator()的作用是返回了一個itr對象賦值到var2,后面調(diào)用var2.hasnext()var2.next()就是itr的具體實現(xiàn)了。

這里還值的一提的是expectedmodcount, 這個變量記錄被賦值為modcount, modcount是arraylist的父類abstractlist的一個字段,這個字段的含義是list結(jié)構(gòu)發(fā)生變更的次數(shù),通常是add或remove等導致元素數(shù)量變更的會觸發(fā)modcount++。

下面接著看itr.hasnext()``var2.next()的實現(xiàn)。

itr.hasnext 和 itr.next 實現(xiàn)

hasnext很簡單

?
1
2
3
public boolean hasnext() {
 return cursor != size;
 }

當前index不等于size則說明還沒迭代完,這里的size是外部類arraylist的字段,表示元素個數(shù)。

在看next實現(xiàn):

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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];
 }
 
final void checkforcomodification() {
 if (modcount != expectedmodcount)
 throw new concurrentmodificationexception();
 }

next方法第一步 checkforcomodification() ,它做了什么? 如果modcount != expectedmodcount就拋出異常concurrentmodificationexception。modcount是什么?外部類arraylist的元素數(shù)量變更次數(shù);expectedmodcount是什么?初始化內(nèi)部類itr的時候外部類的元素數(shù)量變更次數(shù)。

所以,如果在foreach中做了add或者remove操作會導致程序異常concurrentmodificationexception。這里可以走兩個例子:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@test(expected = concurrentmodificationexception.class)
public void testlistforeachremovethrow() {
list<string> list = new arraylist<>();
list.add("1");
list.add("2");
list.add("3");
 
for (string s : list) {
list.remove(s);
}
}
 
@test(expected = concurrentmodificationexception.class)
public void testlistforeachaddthrow() {
list<string> list = new arraylist<>();
list.add("1");
list.add("2");
list.add("3");
 
for (string s : list) {
list.add(s);
}
}

單元測試跑過,都拋了concurrentmodificationexception。

checkforcomodification()之后的代碼比較簡單這里就不分析了。

倒數(shù)第二個元素的特殊

到這里我們來捋一捋大致的流程:

獲取到itr對象賦值給var2

判斷hasnext,也就是判斷cursor != size,當前迭代元素下標不等于list的個數(shù),則返回true繼續(xù)迭代;反之退出循環(huán)

next取出迭代元素

  • checkforcomodification() ,判斷modcount != expectedmodcount,元素數(shù)量變更次數(shù)不等于初始化內(nèi)部類itr的時元素變更次數(shù),也就是在迭代期間做過修改就拋concurrentmodificationexception。
  • 如果檢查通過cursor++

下面考慮一種情況:remove了倒數(shù)第二個元素會發(fā)生什么?代碼如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@test
public void testlistforeachremoveback2notthrow() {
 list<string> list = new arraylist<>();
 list.add("1");
 list.add("2");
 list.add("3");
 
 for (string s : list) {
 system.out.println(s);
 if ("2".equals(s)) {
 list.remove(s);
 }
 }
}

猜一下會拋出異常嗎?答案是否定的。輸出為:

1
2

發(fā)現(xiàn)少了3沒有輸出。 分析一下

在倒數(shù)第二個元素"2"remove后,list的size-1變?yōu)榱?,而此時itr中的cur在next方法中取出元素"2"后,做了加1,值變?yōu)?了,導致下次判斷hasnext時,cursor==size,hasnext返回false,最終最后一個元素沒有被輸出。

如何避坑

foreach中remove 或 add 有坑,

  • 在foreach中做導致元素個數(shù)發(fā)生變化的操作(remove, add等)時,會拋出concurrentmodificationexception異常
  • 在foreach中remove倒數(shù)第二個元素時,會導致最后一個元素不被遍歷

那么我們?nèi)绾伪苊饽兀坎荒苡胒oreach我們就用fori嘛,如下代碼:

?
1
2
3
4
5
6
7
8
9
10
11
12
@test
 public void testlistforimiss() {
 list<string> list = new arraylist<>();
 list.add("1");
 list.add("2");
 list.add("3");
 
 for (int i = 0; i < list.size(); i++) {
  system.out.println(list.get(i));
  list.remove(i);
 }
 }

很明顯上面是一個錯誤的示范,輸出如下:

1
3

原因很簡單,原來的元素1被remove后,后面的向前拷貝,2到了原來1的位置(下標0),3到了原來2的位置(下標1),size由3變2,i+1=1,輸出list.get(1)就成了3,2被漏掉了。

下面說下正確的示范:

方法一,還是fori,位置前挪了減回去就行了, remove后i--:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
@test
 public void testlistforiright() {
 list<string> list = new arraylist<>();
 list.add("1");
 list.add("2");
 list.add("3");
 
 for (int i = 0; i < list.size(); i++) {
  system.out.println(list.get(i));
  list.remove(i);
  i--; //位置前挪了減回去就行了
 }
 }

方法二,不用arraylist的remove方法,用itr自己定義的remove方法,代碼如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@test
 public void testiteratorremove() {
 list<string> list = new arraylist<>();
 list.add("1");
 list.add("2");
 list.add("3");
 
 iterator<string> itr = list.iterator();
 while (itr.hasnext()) {
  string s = itr.next();
  system.out.println(s);
  itr.remove();
 }
 }

為什么itr自己定義的remove就不報錯了呢?看下源碼:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void remove() {
  if (lastret < 0)
  throw new illegalstateexception();
  //依然有校驗數(shù)量是否變更
  checkforcomodification();
 
  try {
  arraylist.this.remove(lastret);
  cursor = lastret;
  lastret = -1;
  //但是變更之后重新賦值了,又相等了
  expectedmodcount = modcount;
  } catch (indexoutofboundsexception ex) {
  throw new concurrentmodificationexception();
  }
 }

依然有 checkforcomodification()校驗,但是看到后面又重新賦值了,所以又相等了。

ok,以上就是全部內(nèi)容。介紹了foreach中l(wèi)ist remove的坑,以及如何避免。

總結(jié)

以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務(wù)器之家的支持。

原文鏈接:https://www.cnblogs.com/chrischennx/p/9610853.html

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 亚洲白嫩在线观看 | 成人免费淫片视频观 | 激情宗合 | 日日cao| 久久中文免费 | 欧美一区中文字幕 | 乱淫67194| 精品国产一区二区亚洲人成毛片 | 久久精品国产清自在天天线 | www噜噜偷拍在线视频 | 中文字幕欧美专区 | 成人黄色小视频网站 | av在线一区二区三区四区 | 成人九色 | 国产精品亚洲精品日韩已方 | 成人 日韩| 黄色免费小视频网站 | 老师你怎么会在这第2季出现 | 国产妞干网 | 羞羞电影网 | 91精品国产91久久久久久丝袜 | 水多视频在线观看 | 性欧美xx | 小视频免费在线观看 | 亚州成人在线观看 | 久久蜜桃香蕉精品一区二区三区 | 日本黄色免费观看视频 | 国产精品久久久久久久久久三级 | 国产在线精品一区二区 | 黄色成人小视频 | 免费观看视频在线观看 | 最新在线黄色网址 | 国产精品热 | 欧美精品一级 | 中文字幕国 | 久久艹逼| 女人裸体让男人桶全过程 | 黄色电影免费提供 | 国产亚洲精品久久久久久大师 | 日韩a毛片免费观看 | 538任你躁在线精品视频网站 |