小爱今天去面试了,有一道笔试题没有答对,估计黄了。
题目是这样的
1List<Integer> list = new ArrayList<>();
2list.add(1);
3list.add(2);
4for (Integer temp : list) {
5 if(1==temp){
6 list.remove(temp);
7 }
8}
9System.out.println(list);
输出结果:[2]
要是把上面第5行的1改成2
输出结果是什么:
小爱选择:[1]
面试官说你回去试试。
回来试了下,抛出异常
Exception in thread "main" java.util.ConcurrentModificationException
这是什么情况?
原来foreach 的写法实际上是对的 Iterable、hasNext、next方法的简写。于是查找List.iterator()源码,跟踪iterator()方法,发现该方法返回了 Itr 迭代器对象。
public Iterator<E> iterator() {
return listIterator();
}
Itr 类源码
1private class Itr implements Iterator<E> {
2 /**
3 * Index of element to be returned by subsequent call to next.
4 */
5int cursor = 0;
6
7
8/**
9 * Index of element returned by most recent call to next or
10 * previous. Reset to -1 if this element is deleted by a call
11 * to remove.
12 */
13int lastRet = -1;
14
15
16/**
17 * The modCount value that the iterator believes that the backing
18 * List should have. If this expectation is violated, the iterator
19 * has detected concurrent modification.
20 */
21int expectedModCount = modCount;
22
23
24public boolean hasNext() {
25 return cursor != size();
26}
27
28
29public E next() {
30 checkForComodification();
31 try {
32 int i = cursor;
33 E next = get(i);
34 lastRet = i;
35 cursor = i + 1;
36 return next;
37 } catch (IndexOutOfBoundsException e) {
38 checkForComodification();
39 throw new NoSuchElementException();
40 }
41}
42
43
44public void remove() {
45 if (lastRet < 0)
46 throw new IllegalStateException();
47 checkForComodification();
48
49
50 try {
51 AbstractList.this.remove(lastRet);
52 if (lastRet < cursor)
53 cursor--;
54 lastRet = -1;
55 expectedModCount = modCount;
56 } catch (IndexOutOfBoundsException e) {
57 throw new ConcurrentModificationException();
58 }
59}
60
61
62final void checkForComodification() {
63 if (modCount != expectedModCount)
64 throw new ConcurrentModificationException();
65 }
66}
通过源码我们知道Itr 是 ArrayList 中定义的一个私有内部类,在 next、remove方法中都会调用checkForComodification 方法,该方法的作用是判断 modCount != expectedModCount是否相等,要是不相等就抛出ConcurrentModificationException异常。
每次正常执行remove 方法后,都会对执行expectedModCount = modCount赋值,保证两个值相等,那么问题基本上已经清晰了,在foreach 循环中 执行 list.remove(item);对list对象的 modCount 值进行了修改,而 list 对象的迭代器的 expectedModCount 值未进行修改,所以抛出了ConcurrentModificationException异常。
原来坑这么大,怪不得面试官会提这样的问题。
关于List的remove()方法陷阱,你可能也遇到过。
Java的List在删除元素时,一般会用list.remove(object)/remove(i)方法。在使用时,容易触碰陷阱,得到意想不到的结果。
今天整理出来与大家分享。下面我们写个demo来实践下。
先初始化list
1package com.peng.tree;
2import java.util.ArrayList;
3import java.util.List;
4
5
6public class ListRemoveTest {
7 public static void main(String[] args) {
8 List<Integer> list = new ArrayList<>();
9 list.add(1);
10 list.add(2);
11 list.add(3);
12 list.add(4);
13 list.add(4);
14 list.add(5);
15 System.out.println(list);
16 }
17}
毋庸置疑输出结果:[1, 2, 3, 4, 4, 5]
list中含有两个元素4,我们要把元素4删除。
如果我们用for循环遍历List删除指定元素 ,例如这样:
1 for(int i=0;i<list.size();i++){
2 if(list.get(i)==4) {
3 list.remove(i);
4 }
5 }
6 System.out.println(list);
输出结果: [1, 2, 3, 4, 5]
细心的你会发现,元素4只删除了一个,还有一个元素4并没有删除, 可见这种做法是不可取的。
为什么元素4只删除了一个?因为List调用remove(index)方法后,会移除index位置上的元素,index之后的元素就全部依次左移。
既然这样,那我们是否可以在删除元素后同步调整索引或者倒序遍历删除元素?
先尝试删除元素后同步调整索引
1for(int i=0;i<list.size();i++){
2 if(list.get(i)==4) {
3 list.remove(i);
4 i--;
5 }
6 }
7System.out.println(list);
输出结果:[1, 2, 3, 5]
两个元素4都正常删掉了。
再来看看倒序遍历List删除元素
for(int i=list.size()-1;i>=0;i--){
if(list.get(i)==4){
list.remove(i);
}
}
System.out.println(list);
输出结果:[1, 2, 3, 5] 也是可行的。
前面我们提到用foreach,出现了异常,我们再来验证下。
foreach遍历List删除元素
for(Integer i:list){
if(i==4) {
list.remove(i);
}
}
System.out.println(list);
结果不出所料,抛出异常:
Exception in thread "main" java.util.ConcurrentModificationException
用迭代删除List元素
Iterator<Integer> it=list.iterator();
while(it.hasNext()){
if(it.next()==4){
it.remove();
}
}
System.out.println(list);
输出结果:[1, 2, 3, 5]
Iterator.remove() 方法会在删除当前迭代对象的同时,会保留原来元素的索引。所以用迭代删除元素是最保险的方法,建议大家使用List过程中需要删除元素时,使用这种方式。
我们再用迭代遍历,用list.remove(i)方法删除元素试试。
Iterator<Integer> it=list.iterator();
while(it.hasNext()){
Integer value=it.next();
if(value==4){
list.remove(value);
}
}
System.out.println(list);
结果抛出异常:
java.util.ConcurrentModificationException
原理同上。
还有一点我们需要注意的是,List删除元素时,需要留意Integer类型和int类型的区别。
例如上述Integer的list,如果我们想要直接删除元素4,要是我们这样写
list.remove(2);
System.out.println(list);
输出结果:[1, 2, 4, 4, 5]
从输出结果得知,并没有把元素4删掉,而是3元素删掉了,List删除元素时传入数字时,默认按索引删除。如果需要删除Integer对象,调用remove(object)方法,需要传入Integer类型。
list.remove(new Integer(2));
System.out.println(list)
输出结果:[1, 3, 4, 4, 5]
这才是真正删除掉元素2。
总结:
用for循环遍历List删除元素时,需要注意索引会左移的问题。
List删除元素时,为避免异常,建议使用迭代器iterator的remove方式。
List删除元素时,默认按索引删除,而不是对象删除。
不要在 foreach 循环里进行元素的 remove 操作。remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁。
今天的分享就到这里,要是本文对你学习有帮助,请帮忙点【在看】。
-END-
作者:洪生鹏 擅长java,qt、Android、小程序平台开发。技术交流请加鹏哥微信: hsp-88ios
猜你喜欢
更多惊喜,请长按二维码识别关注
你若喜欢,别忘了帮忙点【在看】