for、foreach、iterator小坑

在使用for、foreach和iterator时,三者存在一些区别,当我们在遍历中对遍历对象进行修改时,会出现问题。

在开发中要避免在遍历中队遍历对象进行修改,如果需要,应该使用iterator安全地对对象进行删除操作

1. 三者对比

1.1.for

当我们使用for循环对list进行遍历时,如果途中删除了某个元素,那么会导致遍历次数减少

比如下面本应遍历1~5,但由于在2时,list.size减少了1,所以在遍历到4时便结束了,5没有被遍历到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.Iterator;

public class MyTest {
@Test
public void test() {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);

int count = 0;

for (int i =0;i<list.size();i++){
count++;
if(list.get(i)==2){
list.remove(i);
}
}

System.out.println("循环次数:"+count);
//遍历次数为 4

}
}

1.2.foreach

foreach底层实际是利用了iterator来完成的,所以当我们在遍历时修改了遍历的list,就会抛出ConcurrentModificationException异常

这是因为在迭代器遍历执行next()方法时会验证modCountexpectedModCount是否相同

当我们调用list.remove()时,modeCount会+1,而expectedModCount不变,这就导致了报错。

所以当我们需要移除时,应该使用迭代器中的iterator.remove()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.Iterator;

public class MyTest {
@Test
public void test() {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
//foreach遍历
for(Integer i:list){
if(i==2){
list.remove(i);
}
}
//报错 ConcurrentModificationException
}
}

1.3.iterator

使用iterator.remove()移除元素时modCountexpectedModCount都会+1,这样就可以保证两者相同。

需要注意,在迭代过程中对元素进行修改会导致modCount+1,此时同样会抛出异常!

所以,如果需要修改元素,可以先删除,在迭代完成后再统一添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.Iterator;

public class MyTest {
@Test
public void test() {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);

int count = 0;
//迭代器遍历
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
count++;
Integer next = iterator.next();
if(next==2){
iterator.remove();
}
}
//循环次数 5
//正常执行无报错
System.out.println("循环次数:"+count);
}

}

2.实际使用

下面是一段在开发过程中对一个jsonObject处理的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//清理空格 -- 为了清理掉key中多余的空格,转为JsonObject再转回json字符串,
JSONObject jsonObject = JSONObject.parseObject(genChart);

//由于需要改变key,所以需要用迭代器来完成安全的遍历,使用iterator.remove来安全地移除key
//迭代器
Iterator<String> iterator = jsonObject.keySet().iterator();

//如果是在迭代遍历中修改key,会抛出ConcurrentModificationException异常
//所以需要暂存需要替换的key和value,在迭代遍历结束后,再统一修改。
HashMap replaceMap = new HashMap();

//遍历
while (iterator.hasNext()){
String key = iterator.next();
String newKey = key.trim();
if (!newKey.equals(key)) {
//如果key有修改,将新key暂存在replaceMap里
replaceMap.put(newKey,jsonObject.get(key));
//删除旧的key
iterator.remove();
}
}

//删除老key后,将新的key和value添加到jsonObject中
jsonObject.putAll(replaceMap);
//转回json字符串
String newGenChart = JSON.toJSONString(jsonObject);
//返回结果
result.put("genChart",newGenChart);