Java中的forEach循環(huán)詳細解讀
前言
相信大家肯定都看過阿里巴巴開發(fā)手冊,而在阿里巴巴開發(fā)手冊中明確的指出,不要再foreach循環(huán)里面進行元素的add和remove,如果你非要進行remove元素,那么請使用Iterator方式,如果存在并發(fā),那么你一定要選擇加鎖。
foreach
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("11");
list.add("22");
list.add("33");
list.add("44");
for (String s : list) {
if ("22".equalsIgnoreCase(s)) {
list.remove(s);
}
}
System.out.println(JSONObject.toJSONString(list));
}輸出結果:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at org.example.list.Test01.main(Test01.java:22)
Process finished with exit code 1
分析異常:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}比較兩個值 modCount 和expectedModCount,那么這兩個變量是什么呢?
其中modCount表示集合的修改次數(shù),這其中包括了調(diào)用集合本身的add方法等修改方法時進行的修改和調(diào)用集合迭代器的修改方法進行的修改。而expectedModCount則是表示迭代器對集合進行修改的次數(shù)。
先來看看反編譯之后的代碼,如下:
public static void main(String[] args) {
List<String> list = new ArrayList();
list.add("11");
list.add("22");
list.add("33");
list.add("44");
Iterator var2 = list.iterator();
while(var2.hasNext()) {
String s = (String)var2.next();
if ("22".equalsIgnoreCase(s)) {
list.remove(s);
}
}
System.out.println(JSONObject.toJSONString(list));
}看里面使用的也是迭代器,也就是說,其實 foreach 每次循環(huán)都調(diào)用了一次iterator的next()方法, foreach方式中調(diào)用的remove方法,是ArrayList內(nèi)部的remove方法,會更新modCount屬性
我們可以看看ArrayList類中的remove方法
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}看到此方法中,有一個modCount++的操作,也就是說,modCount會一直更新變化。
我們第一次迭代的時候 11 != 22 ,直接迭代第二次,這時候就相等了,執(zhí)行remove()方法,這時候就是modCount++,再次調(diào)用next()的時候,modCount = expectedModCount 這個就不成立了,所以異常信息出現(xiàn)了,其實也可以理解為在 hasNext() 里面,cursor != size 而這時候就會出現(xiàn)錯誤了。
也就是說 remove方法它只修改了modCount,并沒有對expectedModCount做任何操作。
迭代器
為什么阿里巴巴的規(guī)范手冊會這樣子定義?

它為什么推薦我們使用 Iterator呢?
直接使用迭代器會修改expectedModCount,而我們使用foreach的時候,remove方法它只修改了modCount,并沒有對expectedModCount做任何操作,而Iterator就不會這個樣子。
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("11");
list.add("22");
list.add("33");
list.add("44");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
String item = iterator.next();
if("22".equals(item)){
iterator.remove();
}
}
System.out.println(JSONObject.toJSONString(list));
}輸出結果:
["11","33","44"]
Process finished with exit code 0
可以看出結果是正確的,下面我們來分析一下:
先來看看反編譯之后的代碼:
public static void main(String[] args) {
List<String> list = new ArrayList();
list.add("11");
list.add("22");
list.add("33");
list.add("44");
Iterator iterator = list.iterator();
while(iterator.hasNext()) {
String item = (String)iterator.next();
if ("22".equals(item)) {
iterator.remove();
}
}
System.out.println(JSONObject.toJSONString(list));
}主要觀察remove()方法的實現(xiàn),那么需要先看 ArrayList.class:
public Iterator<E> iterator() {
return new Itr();
}
/**
* An optimized version of AbstractList.Itr
*/
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
Itr() {}
public boolean hasNext() {
return cursor != size;
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification(); //第一步
try {
ArrayList.this.remove(lastRet); //第二步:調(diào)用list的remove方法
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount; //第三步:modCount是remove方法去維護更新,
//由于第一步中校驗 modCount 和 expectedModCount 是否相當?shù)?
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}- 調(diào)用 checkForComodification()方法,作用:判斷modCount 和 expectedModCount 是否相當;
- foreach 方式中調(diào)用的remove方法,是ArrayList內(nèi)部的remove方法,會更新modCount屬性;
- 將更新后的modCount重新賦值給expectedModCount變量。
Java8的新特性
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("11");
list.add("22");
list.add("33");
list.add("44");
list.removeIf("22"::equals);
System.out.println(JSONObject.toJSONString(list));
}總結
for-each循環(huán)不僅適用于遍歷集合和數(shù)組,而且能讓你遍歷任何實現(xiàn)Iterator接口的對象;最最關鍵的是它還沒有性能損失。
而對數(shù)組或集合進行修改(添加刪除操作),就要用迭代器循環(huán)。所以循環(huán)遍歷所有數(shù)據(jù)的時候,能用它的時候還是選擇它吧。
到此這篇關于Java中的forEach循環(huán)詳細解讀的文章就介紹到這了,更多相關forEach循環(huán)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
在springboot中如何集成clickhouse進行讀寫操作
本文介紹了在Spring Boot中集成ClickHouse的步驟,包括引入依賴、配置數(shù)據(jù)源、編寫實體類和Mapper類進行CRUD操作,特別提到批量插入時需要在SQL語句中添加`FORMAT`以避免錯誤,在實際應用中,與MySQL的操作類似,只需將ClickHouse當作MySQL使用2024-11-11
springboot validator枚舉值校驗功能實現(xiàn)
這篇文章主要介紹了springboot validator枚舉值校驗功能實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-01-01

