您好,登錄后才能下訂單哦!
本文小編為大家詳細介紹“JAVA中簡單的for循環異常怎么解決”,內容詳細,步驟清晰,細節處理妥當,希望這篇“JAVA中簡單的for循環異常怎么解決”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學習新知識吧。
實際的業務項目開發中,大家應該對從給定的list中剔除不滿足條件的元素這個操作不陌生吧?
很多同學可以立刻想出很多種實現的方式,但你想到的這些實現方式都是人畜無害的嗎?很多看似正常的操作其實背后是個陷阱,很多新手可能稍不留神就會掉入其中。
倘若不幸踩中:
代碼運行時直接拋異常報錯,這個算是不幸中的萬幸,至少可以及時發現并去解決
代碼運行不報錯,但是業務邏輯莫名其妙的出現各種奇怪問題,這種就比較悲劇了,因為這個問題稍不留神的話,可能就會給后續業務埋下隱患。
那么,到底有哪些實現方式呢?哪些實現方式可能會存在問題呢?這里我們一起探討下。注意哦,這里討論的可不是茴香豆的“茴”字有有種寫法的問題,而是很嚴肅很現實也很容易被忽略的技術問題。
假設需求場景:
給定一個用戶列表allUsers,需要從該列表中剔除隸屬部門為dev的人員,將剩余的人員信息返回
很多新手的第一想法就是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異常。
嗯哼?既然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中元素變更之后,其每個元素對應的下標都可能會變更,如下示意:
那么,從List中刪除元素之后,List中被刪元素后面的所有元素下標都發生前移,但是for循環的指針i是始終往后累加的,再處理下一個的時候,就可能會有部分元素被漏掉沒有處理。
比如下圖的示意,i=0時,判斷A元素需要刪除,則直接刪除;再循環時i=1,此時因為list中元素位置前移,導致B元素變成了原來下標為0的位置,直接被漏掉了:
所以到這里呢,也就可以知道為啥上面的代碼執行后會出現漏網之魚啦~
見識了上面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,所以在迭代器中增加、刪除元素是可以正常運行的。,所以這樣就不會出問題啦。
言簡意賅,直接上代碼:
public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) { allUsers.removeIf(user -> "dev".equals(user.getDepartment())); return allUsers; }
作為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循環異常怎么解決”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。