目錄
引言
踩坑操作
foreach循環剔除方式
下標循環運算
正確方式
迭代器方式
Lambda表达式
Stream流操作
中间对象辅助方式
首頁 Java java教程 如何避免JAVA中簡單的for迴圈出現異常?

如何避免JAVA中簡單的for迴圈出現異常?

Apr 26, 2023 pm 12:58 PM
java for

引言

實際的業務專案開發中,大家應該對從給定的list中剔除不滿足條件的元素這個操作不陌生吧?

很多同學可以立刻想出很多實現的方式,但你想到的這些實現方式都是人畜無害的嗎?很多看似正常的操作其實背後是個陷阱,很多新手可能稍不留神就會掉進去。

倘若不幸踩中:

  • 程式碼運行時直接拋異常報錯,這個算是不幸中的萬幸,至少可以及時發現並去解決

  • #程式碼運作不報錯,但是業務邏輯莫名其妙的出現各種奇怪問題,這種就比較悲劇了,因為這個問題稍不留神的話,可能就會給後續業務埋下隱患。

那麼,到底有哪些實作方式呢?哪些實現方式可能會有問題呢?這裡我們一起探討下。注意哦,這裡討論的可不是茴香豆的「壕」字有種寫法的問題,而是很嚴肅很現實也很容易被忽略的技術問題。

假設需求場景:

給定一個使用者清單allUsers,需要從這個清單中剔除隸屬部門為dev的人員,將剩餘的人員資訊回傳

踩坑操作

foreach循環剔除方式

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

1分鐘就把程式碼寫完了:

public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) {
    for (UserDetail user : allUsers) {
        // 判断部门如果属于dev,则直接剔除
        if ("dev".equals(user.getDepartment())) {
            allUsers.remove(user);
        }
    }
    // 返回剩余的用户数据
    return allUsers;
}
登入後複製

然後信心滿滿的點擊了執行按鈕:

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進行實現的。

在迴圈開始時,會先建立一個迭代實例,這個迭代實例的expectedModCount 賦值為集合的modCount。而每當迭代器讓⽤ hashNext() / next() 遍歷下⼀個元素之前,都會偵測 modCount 變數與expectedModCount 值是否相等,相等的話就回傳遍歷;否則就拋出異常ConcurrentModificationException,終⽌遍歷。

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

如何避免JAVA中簡單的for迴圈出現異常?

下標循環運算

嗯哼?既然foreach方式不行,那就用原始的下標迴圈的方式來搞,總不會報錯了吧?依舊很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);
        }
    }
    // 返回剩余的用户数据
    return allUsers;
}
登入後複製

程式碼一氣呵成,執行一下,看下處理後的輸出:

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

#果然,不報錯了,結果也輸出了,完美~

等等?這樣真的OK了嗎?

我們的程式碼邏輯裡面是判斷如果"dev".equals(department),但是輸出結果裡面,為啥還是有department=dev這種本應被剔除掉的資料呢?

這裡如果是在真實業務專案中,開發階段不報錯,又沒有仔細去驗證結果的情況下,流到生產線上,就可能造成業務邏輯的異常。

接下來看下出現這個現象的具體原因。

原因分析:

我們知道,list中的元素與下標之間,其實並沒有強綁定關係,僅僅只是一個位置順序的對應關係,list中元素變更之後,其每個元素對應的下標都可能會變更,如下示意:

如何避免JAVA中簡單的for迴圈出現異常?

那麼,從List中刪除元素之後,List中被刪元素後面的所有元素下標都發生前移,但是for迴圈的指標i是總是往後累加的,再處理下一個的時候,就可能會有部分元素被漏掉沒有處理。

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

如何避免JAVA中簡單的for迴圈出現異常?

所以到這裡呢,也就可以知道為啥上面的程式碼執行後會出現漏網之魚啦~

正確方式

見識了上面2個坑操作之後,那正確妥當的操作方式應該是怎麼樣的呢?

迭代器方式

誒?沒錯吧?前面不是剛剛說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();
        }
    }
    // 返回剩余的用户数据
    return allUsers;
}
登入後複製

执行结果:

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

这次竟然直接执行成功了,且结果也是正确的。为啥呢?

在前面foreach方式的时候,我们提过之所以会报错的原因,是由于直接修改了原始list数据而没有同步让Iterator感知到,所以导致Iterator操作前校验失败抛异常了。

而此处的写法中,直接调用迭代器中的remove()方法,此操作会在调用集合的remove(),add()方法后,将expectedModCount重新赋值为modCount,所以在迭代器中增加、删除元素是可以正常运行的。,所以这样就不会出问题啦。

如何避免JAVA中簡單的for迴圈出現異常?

Lambda表达式

言简意赅,直接上代码:

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

Stream流操作

作为JAVA8开始加入的Stream,使得这种场景实现起来更加的优雅与易懂:

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

中间对象辅助方式

既然前面说了不能直接循环的时候执行移除操作,那就先搞个list对象将需要移除的元素暂存起来,最后一起剔除就行啦 ~

嗯,虽然有点挫,但是不得不承认,实际情况中,很多人都在用这个方法 —— 说的就是你,你是不是也曾这么写过?

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迴圈出現異常?的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
威爾R.E.P.O.有交叉遊戲嗎?
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

Java 中的完美數 Java 中的完美數 Aug 30, 2024 pm 04:28 PM

Java 完美數指南。這裡我們討論定義,如何在 Java 中檢查完美數?

Java 中的隨機數產生器 Java 中的隨機數產生器 Aug 30, 2024 pm 04:27 PM

Java 隨機數產生器指南。在這裡,我們透過範例討論 Java 中的函數,並透過範例討論兩個不同的生成器。

Java中的Weka Java中的Weka Aug 30, 2024 pm 04:28 PM

Java 版 Weka 指南。這裡我們透過範例討論簡介、如何使用 weka java、平台類型和優點。

Java 中的史密斯數 Java 中的史密斯數 Aug 30, 2024 pm 04:28 PM

Java 史密斯數指南。這裡我們討論定義,如何在Java中檢查史密斯號?帶有程式碼實現的範例。

Java Spring 面試題 Java Spring 面試題 Aug 30, 2024 pm 04:29 PM

在本文中,我們保留了最常被問到的 Java Spring 面試問題及其詳細答案。這樣你就可以順利通過面試。

突破或從Java 8流返回? 突破或從Java 8流返回? Feb 07, 2025 pm 12:09 PM

Java 8引入了Stream API,提供了一種強大且表達力豐富的處理數據集合的方式。然而,使用Stream時,一個常見問題是:如何從forEach操作中中斷或返回? 傳統循環允許提前中斷或返回,但Stream的forEach方法並不直接支持這種方式。本文將解釋原因,並探討在Stream處理系統中實現提前終止的替代方法。 延伸閱讀: Java Stream API改進 理解Stream forEach forEach方法是一個終端操作,它對Stream中的每個元素執行一個操作。它的設計意圖是處

Java 中的時間戳至今 Java 中的時間戳至今 Aug 30, 2024 pm 04:28 PM

Java 中的時間戳記到日期指南。這裡我們也結合範例討論了介紹以及如何在java中將時間戳記轉換為日期。

Java程序查找膠囊的體積 Java程序查找膠囊的體積 Feb 07, 2025 am 11:37 AM

膠囊是一種三維幾何圖形,由一個圓柱體和兩端各一個半球體組成。膠囊的體積可以通過將圓柱體的體積和兩端半球體的體積相加來計算。本教程將討論如何使用不同的方法在Java中計算給定膠囊的體積。 膠囊體積公式 膠囊體積的公式如下: 膠囊體積 = 圓柱體體積 兩個半球體體積 其中, r: 半球體的半徑。 h: 圓柱體的高度(不包括半球體)。 例子 1 輸入 半徑 = 5 單位 高度 = 10 單位 輸出 體積 = 1570.8 立方單位 解釋 使用公式計算體積: 體積 = π × r2 × h (4

See all articles