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

JAVA中簡單的for循環(huán)異常踩坑

 更新時間:2022年07月18日 15:58:20   作者:架構(gòu)悟道  
這篇文章主要為大家介紹了JAVA中簡單的for循環(huán)異常踩坑避雷詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

引言

實際的業(yè)務(wù)項目開發(fā)中,大家應(yīng)該對從給定的list中剔除不滿足條件的元素這個操作不陌生吧?

很多同學(xué)可以立刻想出很多種實現(xiàn)的方式,但你想到的這些實現(xiàn)方式都是人畜無害的嗎?很多看似正常的操作其實背后是個陷阱,很多新手可能稍不留神就會掉入其中。

倘若不幸踩中:

  • 代碼運(yùn)行時直接拋異常報錯,這個算是不幸中的萬幸,至少可以及時發(fā)現(xiàn)并去解決
  • 代碼運(yùn)行不報錯,但是業(yè)務(wù)邏輯莫名其妙的出現(xiàn)各種奇怪問題,這種就比較悲劇了,因為這個問題稍不留神的話,可能就會給后續(xù)業(yè)務(wù)埋下隱患。

那么,到底有哪些實現(xiàn)方式呢?哪些實現(xiàn)方式可能會存在問題呢?這里我們一起探討下。注意哦,這里討論的可不是茴香豆的“茴”字有有種寫法的問題,而是很嚴(yán)肅很現(xiàn)實也很容易被忽略的技術(shù)問題。

假設(shè)需求場景:

給定一個用戶列表allUsers,需要從該列表中剔除隸屬部門為dev的人員,將剩余的人員信息返回

踩坑操作

foreach循環(huán)剔除方式

很多新手的第一想法就是for循環(huán)逐個判斷校驗下然后符合條件的剔除掉就行了嘛~ so easy...

1分鐘就把代碼寫完了:

public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) {
    for (UserDetail user : allUsers) {
        // 判斷部門如果屬于dev,則直接剔除
        if ("dev".equals(user.getDepartment())) {
            allUsers.remove(user);
        }
    }
    // 返回剩余的用戶數(shù)據(jù)
    return allUsers;
}

然后信心滿滿的點擊了執(zhí)行按鈕:

java.util.ConcurrentModificationException: null
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
	at java.util.ArrayList$Itr.next(ArrayList.java:859)
	at com.veezean.demo4.UserService.filterAllDevDeptUsers(UserService.java:13)
	at com.veezean.demo4.Main.main(Main.java:26)

誒? what are you 弄啥嘞?咋拋異常了?

一不留神就踩坑里了,下面就一起分析下為啥會拋異常。

原因分析:

JAVA的foreach語法實際處理是基于迭代器Iterator進(jìn)行實現(xiàn)的。

在循環(huán)開始時,會首先創(chuàng)建一個迭代實例,這個迭代實例的expectedModCount 賦值為集合的modCount。而每當(dāng)?shù)魇? hashNext() / next() 遍歷下?個元素之前,都會檢測 modCount 變量與expectedModCount 值是否相等,相等的話就返回遍歷;否則就拋出異常ConcurrentModificationException,終?遍歷。

如果在循環(huán)中添加或刪除元素,是直接調(diào)用集合的add(),remove()方法,導(dǎo)致了modCount增加或減少,但這些方法不會修改迭代實例中的expectedModCount,導(dǎo)致在迭代實例中expectedModCount與 modCount的值不相等,拋出ConcurrentModificationException異常。

下標(biāo)循環(huán)操作

嗯哼?既然foreach方式不行,那就用原始的下標(biāo)循環(huán)的方式來搞,總不會報錯了吧?依舊很easy ...

public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) {
    for (int i = 0; i < allUsers.size(); i++) {
        // 判斷部門如果屬于dev,則直接剔除
        if ("dev".equals(allUsers.get(i).getDepartment())) {
            allUsers.remove(i);
        }
    }
    // 返回剩余的用戶數(shù)據(jù)
    return allUsers;
}

代碼一氣呵成,執(zhí)行一下,看下處理后的輸出:

{id=2, name='李四', department='dev'}
{id=3, name='王五', department='product'}
{id=4, name='鐵柱', department='pm'}

果然,不報錯了,結(jié)果也輸出了,完美~

等等?這樣真的OK了嗎?

我們的代碼邏輯里面是判斷如果"dev".equals(department),但是輸出結(jié)果里面,為啥還是有department=dev這種本應(yīng)被剔除掉的數(shù)據(jù)呢?

這里如果是在真實業(yè)務(wù)項目中,開發(fā)階段不報錯,又沒有仔細(xì)去驗證結(jié)果的情況下,流到生產(chǎn)線上,就可能造成業(yè)務(wù)邏輯的異常。

接下來看下出現(xiàn)這個現(xiàn)象的具體原因。

原因分析:

我們知道,list中的元素與下標(biāo)之間,其實并沒有強(qiáng)綁定關(guān)系,僅僅只是一個位置順序的對應(yīng)關(guān)系,list中元素變更之后,其每個元素對應(yīng)的下標(biāo)都可能會變更,如下示意:

那么,從List中刪除元素之后,List中被刪元素后面的所有元素下標(biāo)都發(fā)生前移,但是for循環(huán)的指針i是始終往后累加的,再處理下一個的時候,就可能會有部分元素被漏掉沒有處理。

比如下圖的示意,i=0時,判斷A元素需要刪除,則直接刪除;再循環(huán)時i=1,此時因為list中元素位置前移,導(dǎo)致B元素變成了原來下標(biāo)為0的位置,直接被漏掉了:

所以到這里呢,也就可以知道為啥上面的代碼執(zhí)行后會出現(xiàn)漏網(wǎng)之魚啦~

正確方式

見識了上面2個坑操作之后,那正確妥當(dāng)?shù)牟僮鞣绞綉?yīng)該是怎么樣的呢?

迭代器方式

誒?沒搞錯吧?前面不是剛說過foreach方式也是使用的迭代器,但是其實是坑操作嗎?這里怎么又說迭代器模式是正確方式呢?

雖然都是基于迭代器,但是使用邏輯是不一樣的,看下代碼:

public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) {
    Iterator<UserDetail> iterator = allUsers.iterator();
    while (iterator.hasNext()) {
        // 判斷部門如果屬于dev,則直接剔除
        if ("dev".equals(iterator.next().getDepartment())) {
            // 這是重點,此處操作的是Iterator,而不是list
            iterator.remove();
        }
    }
    // 返回剩余的用戶數(shù)據(jù)
    return allUsers;
}

執(zhí)行結(jié)果:

{id=3, name='王五', department='product'}
{id=4, name='鐵柱', department='pm'}

這次竟然直接執(zhí)行成功了,且結(jié)果也是正確的。為啥呢?

在前面foreach方式的時候,我們提過之所以會報錯的原因,是由于直接修改了原始list數(shù)據(jù)而沒有同步讓Iterator感知到,所以導(dǎo)致Iterator操作前校驗失敗拋異常了。

而此處的寫法中,直接調(diào)用迭代器中的remove()方法,此操作會在調(diào)用集合的remove(),add()方法后,將expectedModCount重新賦值為modCount,所以在迭代器中增加、刪除元素是可以正常運(yùn)行的。,所以這樣就不會出問題啦。

Lambda表達(dá)式

言簡意賅,直接上代碼:

public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) {
    allUsers.removeIf(user -> "dev".equals(user.getDepartment()));
    return allUsers;
}

Stream流操作

作為JAVA8開始加入的Stream,使得這種場景實現(xiàn)起來更加的優(yōu)雅與易懂:

public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) {
    return allUsers.stream()
            .filter(user -> !"dev".equals(user.getDepartment()))
            .collect(Collectors.toList());
}

中間對象輔助方式

既然前面說了不能直接循環(huán)的時候執(zhí)行移除操作,那就先搞個list對象將需要移除的元素暫存起來,最后一起剔除就行啦 ~

嗯,雖然有點挫,但是不得不承認(rèn),實際情況中,很多人都在用這個方法 —— 說的就是你,你是不是也曾這么寫過?

public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) {
    List<UserDetail> needRemoveUsers = new ArrayList<>();
    for (UserDetail user : allUsers) {
        if ("dev".equals(user.getDepartment())) {
            needRemoveUsers.add(user);
        }
    }
    allUsers.removeAll(needRemoveUsers);
    return allUsers;
}

或者:

public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) {
    List<UserDetail> resultUsers = new ArrayList<>();
    for (UserDetail user : allUsers) {
        if (!"dev".equals(user.getDepartment())) {
            resultUsers.add(user);
        }
    }
    return resultUsers;
}

以上就是JAVA中簡單的for循環(huán)異常踩坑的詳細(xì)內(nèi)容,更多關(guān)于JAVA for循環(huán)異常的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java中將接口返回的字節(jié)串轉(zhuǎn)為文件詳解

    Java中將接口返回的字節(jié)串轉(zhuǎn)為文件詳解

    這篇文章主要給大家介紹了關(guān)于Java中將接口返回的字節(jié)串轉(zhuǎn)為文件的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2021-11-11
  • Nacos簡介最新收藏版

    Nacos簡介最新收藏版

    Nacos?是阿里巴巴推出來的一個新開源項目,這是一個更易于構(gòu)建云原生應(yīng)用的動態(tài)服務(wù)發(fā)現(xiàn)、配置管理和服務(wù)管理平臺,Nacos?幫助您更敏捷和容易地構(gòu)建、交付和管理微服務(wù)平臺,對Nacos相關(guān)基本知識感興趣的朋友一起看看吧
    2023-08-08
  • 利用Java實現(xiàn)在PDF中添加工具提示

    利用Java實現(xiàn)在PDF中添加工具提示

    這篇文章主要介紹了如何通過Java在PDF中添加工具提示,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)或工作有一定的參考價值,感興趣的可以學(xué)習(xí)一下
    2022-01-01
  • java使用httpclient模擬post請求和get請求示例

    java使用httpclient模擬post請求和get請求示例

    這篇文章主要介紹了java使用httpclient模擬post請求和get請求示例,需要的朋友可以參考下
    2014-02-02
  • Spring-IOC容器中的常用注解與使用方法詳解

    Spring-IOC容器中的常用注解與使用方法詳解

    Spring是一個輕量級Java開發(fā)框架,最早有Rod Johnson創(chuàng)建,目的是為了解決企業(yè)級應(yīng)用開發(fā)的業(yè)務(wù)邏輯層和其他各層的耦合問題,這篇文章給大家詳細(xì)介紹Spring-IOC容器中的常用注解與使用方法,感興趣的朋友跟隨小編一起看看吧
    2021-04-04
  • IntelliJ IDEA本地代碼覆蓋后恢復(fù)原來的代碼圖解

    IntelliJ IDEA本地代碼覆蓋后恢復(fù)原來的代碼圖解

    今天小編就為大家分享一篇關(guān)于IntelliJ IDEA本地代碼覆蓋后恢復(fù)原來的代碼圖解,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2018-10-10
  • Unicode、UTF-8 和 ISO8859-1區(qū)別解析

    Unicode、UTF-8 和 ISO8859-1區(qū)別解析

    這篇文章主要介紹了Unicode、UTF-8 和 ISO8859-1到底有什么區(qū)別,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-01-01
  • SpringBoot給類進(jìn)行賦初值的四種方式

    SpringBoot給類進(jìn)行賦初值的四種方式

    這篇文章主要介紹了springboot給類進(jìn)行賦初值的四種方式,并通過代碼示例給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下
    2024-08-08
  • Java中的垃圾收集器詳解

    Java中的垃圾收集器詳解

    這篇文章主要介紹了Java中的垃圾收集器詳解,垃圾收集機(jī)制是?Java?的招牌能力,極大地提高了開發(fā)效率,如今垃圾收集幾乎成為現(xiàn)代語言的標(biāo)配,需要的朋友可以參考下
    2023-07-07
  • 以Spring Boot的方式顯示圖片或下載文件到瀏覽器的示例代碼

    以Spring Boot的方式顯示圖片或下載文件到瀏覽器的示例代碼

    這篇文章主要介紹了以Spring Boot的方式顯示圖片或下載文件到瀏覽器的示例代碼,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-01-01

最新評論

峨眉山市| 邯郸市| 玛沁县| 衡南县| 黑水县| 施秉县| 雅江县| 乌兰察布市| 平谷区| 福鼎市| 南岸区| 云浮市| 建平县| 万盛区| 河北省| 贵港市| 丹东市| 勃利县| 绥德县| 邯郸市| 宜昌市| 兴仁县| 醴陵市| 宜宾县| 鄯善县| 江门市| 青海省| 辛集市| 襄汾县| 元氏县| 峨边| 房产| 银川市| 营山县| 临泉县| 治县。| 浦东新区| 沾益县| 关岭| 油尖旺区| 韶关市|