最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

面試題:java中為什么foreach中不允許對(duì)元素進(jìn)行add和remove

 更新時(shí)間:2021年10月26日 10:53:48   作者:Q.E.D.  
讀者遇到了一個(gè)比較經(jīng)典的面試題,也就是標(biāo)題上說(shuō)的,為什么 foreach 中不允許對(duì)元素進(jìn)行 add 和 remove,本文就詳細(xì)的介紹一下,感興趣的可以了解一下

1、foreach遍歷ArrayList過(guò)程中使用 add 和 remove

我們先來(lái)看看使用foreach遍歷ArrayList過(guò)程中使用 add 和 remove 會(huì)出現(xiàn)什么樣子的結(jié)果,然后再分析一下。

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);
    }
}

運(yùn)行結(jié)果:

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)

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

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

這個(gè)地方告訴我們?nèi)绻?modCount 不等于 expectedModCount 的時(shí)候,就會(huì)拋出這個(gè)異常信息,那么這兩個(gè)參數(shù)都代表了什么東西呢?為什么不相等的時(shí)候,就會(huì)出現(xiàn)異常呢?

2、追根溯源

2.1、modCount是什么?

這時(shí)候就要讓我們?nèi)タ丛创a了,在我們點(diǎn)到這個(gè)變量的時(shí)候,就會(huì)有注釋告訴我們了 modCount 是 AbstractList 類中的一個(gè)成員變量,該值表示對(duì)List的修改次數(shù)。

這時(shí)候我們來(lái)看看 remove 方法中是否對(duì)這個(gè)變量進(jìn)行了增減。

大家可以看到,在 remove 的方法中,實(shí)際上只是對(duì) modCount 進(jìn)行了++,那 expectedModCount 又是個(gè)什么東西呢?

2.2、expectedModCount 是什么?

expectedModCount 是 ArrayList 中的一個(gè)內(nèi)部類——Itr中的成員變量,我們來(lái)看下怎么又扯出個(gè)內(nèi)部類Itr。

通過(guò)反編譯可以發(fā)現(xiàn)foreach編譯后內(nèi)部是使用迭代器實(shí)現(xiàn)的。

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

2.3、熟悉的checkForComodification方法

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

checkForComodification方法是通過(guò)判斷modCount和expectedModCount是否相等來(lái)決定是否拋出并發(fā)修改異常。

2.4、流程回顧

通過(guò)查看編譯后的class文件,可以看出大致流程如下:當(dāng)j為3時(shí),調(diào)用了remove方法,remove方法中修改了modCount值,然后再輸出j值,再進(jìn)入下一次循環(huán),此時(shí)hasNext為true,進(jìn)入循環(huán)體第一行代碼,調(diào)用next方法,next方法再調(diào)用checkForComodification方法,然后發(fā)現(xiàn)expectedModCount和modCount不一致,最終拋出ConcurrentModificationException 異常。

也就是說(shuō),expectedModCount 初始化為 modCount 了,但是后面 expectedModCount 沒(méi)有修改,而在 remove 和 add 的過(guò)程中修改了modCount ,這就導(dǎo)致了執(zhí)行的時(shí)候,通過(guò) checkForComodification 方法來(lái)判斷兩個(gè)值是否相等,如果相等了,那么沒(méi)問(wèn)題,如果不相等,那就給你拋出一個(gè)異常來(lái)。

而這也就是我們通俗說(shuō)起來(lái)的 fail-fast 機(jī)制,也就是快速檢測(cè)失敗機(jī)制。

3、避免fail-fast 機(jī)制

3.1、使用listIterator或iterator

fail-fast 機(jī)制也是可以避免的,比如再拿出來(lái)我們上面的代碼

public static void main(String[] args) {
    List<Integer> list = new ArrayList<>();
    for (int i = 0; i < 5; i++) {
        list.add(i);
    }
 
    System.out.println("沒(méi)有刪除元素前"+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());
}

這樣的話,你就發(fā)現(xiàn)是可以運(yùn)行的,也是沒(méi)有問(wèn)題的,我們看運(yùn)行結(jié)果:

沒(méi)有刪除元素前[0, 1, 2, 3, 4]
刪除元素后[0, 1, 2, 9, 4]

結(jié)果也是顯而易見(jiàn)的,我們實(shí)現(xiàn)了在 foreach 中進(jìn)行 add 和 remove 的操作.

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

listIterator和iterator區(qū)別:

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

3.2、使用CopyOnWriteArrayList

CopyOnWriteArrayList 這個(gè)類也是能解決 fail-fast 的問(wèn)題的,我們來(lái)試一下:

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

運(yùn)行結(jié)果:

沒(méi)有刪除元素前[0, 1, 2, 3, 4]
刪除元素后[0, 1, 2, 4, 9]

CopyOnWriteArrayList實(shí)現(xiàn)了對(duì)這個(gè)元素中間進(jìn)行移除添加的操作,那么他的內(nèi)部源碼是怎么實(shí)現(xiàn)的,實(shí)際上很簡(jiǎn)單,復(fù)制

也就是他創(chuàng)建一個(gè)新的數(shù)組,再將舊的數(shù)組復(fù)制到新的數(shù)組上,但是為什么很少有人推薦這種做法,根本原因還是 復(fù)制

因?yàn)槟闶褂昧藦?fù)制,那么就一定會(huì)出現(xiàn)有兩個(gè)存儲(chǔ)相同內(nèi)容的空間,這樣消耗了空間,最后進(jìn)行 GC 的時(shí)候,那是不是也需要一些時(shí)間去清理他,所以個(gè)人不是很推薦,但是寫出來(lái)的必要還是有的。

3.2.1、CopyOnWriteArrayList的add方法

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

處理流程如下:

  • 獲取鎖(保證多線程的安全訪問(wèn)),獲取當(dāng)前的Object數(shù)組,獲取Object數(shù)組的長(zhǎng)度為length,進(jìn)入步驟②。
  • 根據(jù)Object數(shù)組復(fù)制一個(gè)長(zhǎng)度為length+1的Object數(shù)組為newElements(此時(shí),newElements[length]為null),進(jìn)入下一步驟。
  • 將下標(biāo)為length的數(shù)組元素newElements[length]設(shè)置為元素e,再設(shè)置當(dāng)前Object[]為newElements,釋放鎖,返回。這樣就完成了元素的添加。

3.2.2、CopyOnWriteArrayList的remove方法

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

處理流程如下:

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

注意

CopyOnWriteArrayList解決 fail-fast 的問(wèn)題不是通過(guò)迭代器來(lái)remove或add元素的,而是通過(guò)list本身的remove和add方法,所以add的元素位置也不一樣,迭代器是當(dāng)前位置后面一個(gè),CopyOnWriteArrayList是直接放到最后。

有想法的同學(xué)可以看看CopyOnWriteArrayList的listIterator和iterator,其實(shí)是一樣的,都是返回的COWIterator內(nèi)部類。

 

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

先寫到這兒,后面有空再補(bǔ)充。

到此這篇關(guān)于面試題:java中為什么foreach中不允許對(duì)元素進(jìn)行add和remove的文章就介紹到這了,更多相關(guān)foreach不允許對(duì)元素進(jìn)行add和remove內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 使用Springboot封裝一個(gè)自適配的數(shù)據(jù)單位轉(zhuǎn)換工具類

    使用Springboot封裝一個(gè)自適配的數(shù)據(jù)單位轉(zhuǎn)換工具類

    我們?cè)诮邮涨芭_(tái)傳輸?shù)臄?shù)據(jù)時(shí),往往SpringBoot使用內(nèi)置的數(shù)據(jù)類型轉(zhuǎn)換器把我們提交的數(shù)據(jù)自動(dòng)封裝成對(duì)象等類型,下面這篇文章主要給大家介紹了關(guān)于使用Springboot封裝一個(gè)自適配的數(shù)據(jù)單位轉(zhuǎn)換工具類的相關(guān)資料,需要的朋友可以參考下
    2023-03-03
  • @RequestBody 部分屬性沒(méi)有轉(zhuǎn)化成功的處理

    @RequestBody 部分屬性沒(méi)有轉(zhuǎn)化成功的處理

    這篇文章主要介紹了@RequestBody 部分屬性沒(méi)有轉(zhuǎn)化成功的處理方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • Druid連接池未關(guān)閉導(dǎo)致內(nèi)存泄漏問(wèn)題

    Druid連接池未關(guān)閉導(dǎo)致內(nèi)存泄漏問(wèn)題

    這篇文章主要介紹了Druid連接池未關(guān)閉導(dǎo)致內(nèi)存泄漏問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • idea顯示properties文件中文亂碼的解決方法

    idea顯示properties文件中文亂碼的解決方法

    在項(xiàng)目中通常會(huì)遇到如下問(wèn)題,突然properties文件中文亂碼,本文主要介紹了idea顯示properties文件中文亂碼的解決方法,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-09-09
  • Java實(shí)現(xiàn)Excel文件加密解密的示例代碼

    Java實(shí)現(xiàn)Excel文件加密解密的示例代碼

    設(shè)置excel文件保護(hù)時(shí),通??蛇x擇對(duì)整個(gè)工作簿進(jìn)行加密保護(hù)。無(wú)需設(shè)置文檔保護(hù)時(shí),可撤銷密碼保護(hù),即解密文檔。本文將通過(guò)java程序演示以上加密、解密方法的實(shí)現(xiàn),感興趣的可以了解一下
    2022-05-05
  • Springboot整合thymleaf模板引擎過(guò)程解析

    Springboot整合thymleaf模板引擎過(guò)程解析

    這篇文章主要介紹了Springboot整合thymleaf模板引擎過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-11-11
  • java線程池ThreadPoolExecutor的八種拒絕策略示例詳解

    java線程池ThreadPoolExecutor的八種拒絕策略示例詳解

    ThreadPoolExecutor是一個(gè)典型的緩存池化設(shè)計(jì)的產(chǎn)物,因?yàn)槌刈佑写笮?當(dāng)池子體積不夠承載時(shí),就涉及到拒絕策略。JDK中已預(yù)設(shè)了?4?種線程池拒絕策略,下面結(jié)合場(chǎng)景詳細(xì)聊聊這些策略的使用場(chǎng)景以及還能擴(kuò)展哪些拒絕策略
    2021-11-11
  • JavaWeb實(shí)現(xiàn)同一帳號(hào)同一時(shí)間只能一個(gè)地點(diǎn)登陸(類似QQ登錄的功能)

    JavaWeb實(shí)現(xiàn)同一帳號(hào)同一時(shí)間只能一個(gè)地點(diǎn)登陸(類似QQ登錄的功能)

    最近做了企業(yè)項(xiàng)目,其中有這樣的需求要求同一帳號(hào)同一時(shí)間只能一個(gè)地點(diǎn)登陸類似QQ登錄的功能。下面小編通過(guò)本文給大家分享實(shí)現(xiàn)思路,感興趣的朋友參考下吧
    2016-11-11
  • Java 在PDF中添加頁(yè)面跳轉(zhuǎn)按鈕功能(代碼演示)

    Java 在PDF中添加頁(yè)面跳轉(zhuǎn)按鈕功能(代碼演示)

    這篇文章主要介紹了Java 在PDF中添加頁(yè)面跳轉(zhuǎn)按鈕功能,本文給大家提供了多種功能的按鈕,需要的朋友可以參考下
    2019-11-11
  • springboot?jpa之返回表中部分字段的處理詳解

    springboot?jpa之返回表中部分字段的處理詳解

    這篇文章主要介紹了springboot?jpa之返回表中部分字段的處理詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-12-12

最新評(píng)論

江门市| 独山县| 丹寨县| 武清区| 长兴县| 永康市| 高碑店市| 南宫市| 托克托县| 班玛县| 石阡县| 阜阳市| 怀仁县| 苍山县| 德令哈市| 方城县| 平武县| 霸州市| 广宁县| 琼结县| 华安县| 边坝县| 津南区| 太谷县| 额尔古纳市| 正安县| 若羌县| 光泽县| 浠水县| 茌平县| 津市市| 萨嘎县| 射洪县| 德昌县| 博湖县| 历史| 宽甸| 本溪| 澄江县| 永德县| 子长县|