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

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

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

服務器之家 - 編程語言 - Java教程 - 面試題:java中為什么foreach中不允許對元素進行add和remove

面試題:java中為什么foreach中不允許對元素進行add和remove

2022-03-01 00:31Q.E.D. Java教程

讀者遇到了一個比較經典的面試題,也就是標題上說的,為什么 foreach 中不允許對元素進行 add 和 remove,本文就詳細的介紹一下,感興趣的可以了解一下

1、foreach遍歷ArrayList過程中使用 add 和 remove

我們先來看看使用foreach遍歷ArrayList過程中使用 add 和 remove 會出現什么樣子的結果,然后再分析一下。

public static void main(String[] args) {
  List<Integer> list = new ArrayList<>();
  for (int i = 0; i < 20; i++) {
      list.add(i);
  }
  for (Integer j : list) {
      if (j.equals(3)) {
          list.remove(3);
      }
      System.out.println(j);
  }
}

運行結果:

0
1
2
3
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
at java.util.ArrayList$Itr.next(ArrayList.java:861)
at test.Test.main(Test.java:12)

結果是出現了ConcurrentModificationException 異常,追蹤下拋出異常的位置(ArrayList.java:911)

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

這個地方告訴我們如果 modCount 不等于 expectedModCount 的時候,就會拋出這個異常信息,那么這兩個參數都代表了什么東西呢?為什么不相等的時候,就會出現異常呢?

 

2、追根溯源

2.1、modCount是什么?

這時候就要讓我們去看源碼了,在我們點到這個變量的時候,就會有注釋告訴我們了 modCount 是 AbstractList 類中的一個成員變量,該值表示對List的修改次數。

這時候我們來看看 remove 方法中是否對這個變量進行了增減。

面試題:java中為什么foreach中不允許對元素進行add和remove

大家可以看到,在 remove 的方法中,實際上只是對 modCount 進行了++,那 expectedModCount 又是個什么東西呢?

2.2、expectedModCount 是什么?

expectedModCount 是 ArrayList 中的一個內部類――Itr中的成員變量,我們來看下怎么又扯出個內部類Itr。

通過反編譯可以發現foreach編譯后內部是使用迭代器實現的。

面試題:java中為什么foreach中不允許對元素進行add和remove

迭代器是通過list.iterator()實例化的,list.iterator()就返回了一個內部類Itr的對象,從源碼中可以看到Itr實現了Iterator接口,同時聲明了expectedModCount這個成員變量, expectedModCount 表示對ArrayList修改次數的期望值,它的初始值為 modCount。

面試題:java中為什么foreach中不允許對元素進行add和remove

2.3、熟悉的checkForComodification方法

從源碼可以看到這個類的next和remove方法里面都調用了一個checkForComodification方法,看到checkForComodification是不是很熟悉,這不就是異常的拋出位置嗎。

面試題:java中為什么foreach中不允許對元素進行add和remove

checkForComodification方法是通過判斷modCount和expectedModCount是否相等來決定是否拋出并發修改異常。

面試題:java中為什么foreach中不允許對元素進行add和remove

2.4、流程回顧

通過查看編譯后的class文件,可以看出大致流程如下:當j為3時,調用了remove方法,remove方法中修改了modCount值,然后再輸出j值,再進入下一次循環,此時hasNext為true,進入循環體第一行代碼,調用next方法,next方法再調用checkForComodification方法,然后發現expectedModCount和modCount不一致,最終拋出ConcurrentModificationException 異常。

面試題:java中為什么foreach中不允許對元素進行add和remove

也就是說,expectedModCount 初始化為 modCount 了,但是后面 expectedModCount 沒有修改,而在 remove 和 add 的過程中修改了modCount ,這就導致了執行的時候,通過 checkForComodification 方法來判斷兩個值是否相等,如果相等了,那么沒問題,如果不相等,那就給你拋出一個異常來。

而這也就是我們通俗說起來的 fail-fast 機制,也就是快速檢測失敗機制。

 

3、避免fail-fast 機制

3.1、使用listIterator或iterator

fail-fast 機制也是可以避免的,比如再拿出來我們上面的代碼

public static void main(String[] args) {
  List<Integer> list = new ArrayList<>();
  for (int i = 0; i < 5; i++) {
      list.add(i);
  }

  System.out.println("沒有刪除元素前"+list.toString());
  // 迭代器使用listIterator和iterator均可
  ListIterator<Integer> listIterator = list.listIterator();
  while(listIterator.hasNext()){
      Integer integer = listIterator.next();
      if(integer==3){
          listIterator.remove();
          listIterator.add(9);
      }
  }
  System.out.println("刪除元素后"+list.toString());
}

這樣的話,你就發現是可以運行的,也是沒有問題的,我們看運行結果:

沒有刪除元素前[0, 1, 2, 3, 4]
刪除元素后[0, 1, 2, 9, 4]

結果也是顯而易見的,我們實現了在 foreach 中進行 add 和 remove 的操作.

這里有個注意點,迭代器使用listIterator和iterator均可,看源碼可以知道 listIterator其實使用的ListItr內部類,ListItr是繼承了Itr類的,同時自己封了一些方法,例如add,hasPrevious,previous等等。所以代碼中的remove方法是Itr類的,add方法是ListItr類的

面試題:java中為什么foreach中不允許對元素進行add和remove

listIterator和iterator區別:

  • 使用范圍不同,Iterator可以應用于所有的集合,Set、List和Map和這些集合的子類型。而ListIterator只能用于List及其子類型。
  • ListIterator有add方法,可以向List中添加對象,而Iterator不能。
  • ListIterator和Iterator都有hasNext()和next()方法,可以實現順序向后遍歷,但是ListIterator有hasPrevious()和previous()方法,可以實現逆向(順序向前)遍歷。Iterator不可以。
  • ListIterator可以定位當前索引的位置,nextIndex()和previousIndex()可以實現。Iterator沒有此功能。
  • 都可實現刪除操作,但是ListIterator可以實現對象的修改,set()方法可以實現。Iterator僅能遍歷,不能修改。

3.2、使用CopyOnWriteArrayList

CopyOnWriteArrayList 這個類也是能解決 fail-fast 的問題的,我們來試一下:

public static void main(String[] args) {
  CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();
  for (int i = 0; i < 5; i++) {
      list.add(i);
  }
  System.out.println("沒有刪除元素前"+list.toString());
  for (Integer integer : list) {
      if(integer.equals(3)){
          list.remove(3);
          list.add(9);
      }
  }
  System.out.println("刪除元素后"+list.toString());
}

運行結果:

沒有刪除元素前[0, 1, 2, 3, 4]
刪除元素后[0, 1, 2, 4, 9]

CopyOnWriteArrayList實現了對這個元素中間進行移除添加的操作,那么他的內部源碼是怎么實現的,實際上很簡單,復制

也就是他創建一個新的數組,再將舊的數組復制到新的數組上,但是為什么很少有人推薦這種做法,根本原因還是 復制

因為你使用了復制,那么就一定會出現有兩個存儲相同內容的空間,這樣消耗了空間,最后進行 GC 的時候,那是不是也需要一些時間去清理他,所以個人不是很推薦,但是寫出來的必要還是有的。

3.2.1、CopyOnWriteArrayList的add方法

public boolean add(E e) {
  // 可重入鎖
  final ReentrantLock lock = this.lock;
  // 獲取鎖
  lock.lock();
  try {
      // 元素數組
      Object[] elements = getArray();
      // 數組長度
      int len = elements.length;
      // 復制數組
      Object[] newElements = Arrays.copyOf(elements, len + 1);
      // 存放元素e
      newElements[len] = e;
      // 設置數組
      setArray(newElements);
      return true;
  } finally {
      // 釋放鎖
      lock.unlock();
  }
}

處理流程如下:

  • 獲取鎖(保證多線程的安全訪問),獲取當前的Object數組,獲取Object數組的長度為length,進入步驟②。
  • 根據Object數組復制一個長度為length+1的Object數組為newElements(此時,newElements[length]為null),進入下一步驟。
  • 將下標為length的數組元素newElements[length]設置為元素e,再設置當前Object[]為newElements,釋放鎖,返回。這樣就完成了元素的添加。

3.2.2、CopyOnWriteArrayList的remove方法

public E remove(int index) {
  // 可重入鎖
  final ReentrantLock lock = this.lock;
  // 獲取鎖
  lock.lock();
  try {
      // 獲取數組
      Object[] elements = getArray();
      // 數組長度
      int len = elements.length;
      // 獲取舊值
      E oldValue = get(elements, index);
      // 需要移動的元素個數
      int numMoved = len - index - 1;
      if (numMoved == 0) // 移動個數為0
          // 復制后設置數組
          setArray(Arrays.copyOf(elements, len - 1));
      else { // 移動個數不為0
          // 新生數組
          Object[] newElements = new Object[len - 1];
          // 復制index索引之前的元素
          System.arraycopy(elements, 0, newElements, 0, index);
          // 復制index索引之后的元素
          System.arraycopy(elements, index + 1, newElements, index,
                              numMoved);
          // 設置索引
          setArray(newElements);
      }
      // 返回舊值
      return oldValue;
  } finally {
      // 釋放鎖
      lock.unlock();
  }
}

處理流程如下:

  • 獲取鎖,獲取數組elements,數組長度為length,獲取索引的值elements[index],計算需要移動的元素個數(length - index - 1),若個數為0,則表示移除的是數組的最后一個元素,復制elements數組,復制長度為length-1,然后設置數組,進入步驟③;否則,進入步驟②
  • 先復制index索引前的元素,再復制index索引后的元素,然后設置數組。
  • 釋放鎖,返回舊值。

 

注意

CopyOnWriteArrayList解決 fail-fast 的問題不是通過迭代器來remove或add元素的,而是通過list本身的remove和add方法,所以add的元素位置也不一樣,迭代器是當前位置后面一個,CopyOnWriteArrayList是直接放到最后。

有想法的同學可以看看CopyOnWriteArrayList的listIterator和iterator,其實是一樣的,都是返回的COWIterator內部類。

面試題:java中為什么foreach中不允許對元素進行add和remove

面試題:java中為什么foreach中不允許對元素進行add和remove

在COWIterator內部類中是不支持remove、set、add操作的,至少我使用的jdk1.8是不支持,會直接拋出UnsupportedOperationException異常:

面試題:java中為什么foreach中不允許對元素進行add和remove

先寫到這兒,后面有空再補充。

到此這篇關于面試題:java中為什么foreach中不允許對元素進行add和remove的文章就介紹到這了,更多相關foreach不允許對元素進行add和remove內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!

原文鏈接:https://blog.csdn.net/qq_34272760/article/details/120953988

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 精品国产乱码久久久久久久久 | 黄网站免费入口 | 狠狠操操 | 精品一区二区三区电影 | 欧美日韩亚洲精品一区二区三区 | 国产真实孩交 | 国产亚洲精品久久 | 奇米影视在线 | 他也色在线视频 | 噜噜色av | 久久91精品国产91久久yfo | 一区二区三区黄色 | av人人| 天堂精品| 黄视频网站免费在线观看 | 中文字幕xxx | 最近高清无吗免费看 | 日韩欧美精品中文字幕 | 蜜桃视频网站在线观看 | 精品国产一区二区三区在线观看 | 日日摸夜夜添夜夜添牛牛 | 精品国产乱码久久久久久丨区2区 | 久久精品99北条麻妃 | 成人mm视频在线观看 | 一本一道久久久a久久久精品91 | 一本一道久久久a久久久精品91 | 96视频在线免费观看 | 欧美成人精品欧美一级 | 久久久久久麻豆 | 亚洲成人欧美 | 色播av在线 | 永久在线观看电影 | 久久蜜桃香蕉精品一区二区三区 | 日本网站在线看 | 久久久裸体视频 | 国产一区二区免费看 | 国产精品亚洲欧美一级在线 | 欧美一级片 在线播放 | 亚洲一区二区中文 | 免费观看一区二区三区视频 | 欧美18一12sex性处hd |