今天去面试,这道笔试题居然没做对,估计黄了

小爱今天去面试了,有一道笔试题没有答对,估计黄了。

题目是这样的

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 

猜你喜欢

还好懂点性能调优,终于找到了一份工作

更多惊喜,请长按二维码识别关注

你若喜欢,别忘了帮忙点【在看

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值