• 为什么在foreach循环中不要对元素进行remove/add操作?


    前言

    在阿里巴巴Java开发手册中,有下面这样的规定:

    这篇文章我们就来深入探讨其中的原因。

    正文

    为什么结果如此不同?

    我们先来看看前言中的反例会出现什么意料之外的结果:

    ----------------------------------------------------------------------------------------------------------------------------------------------------------------------

    仅仅是remove的元素不同,为什么会出现如此不同的结果呢?我们反编译上面报错的字节码文件可得:

    
    import java.io.PrintStream;
    import java.util.*;
    
    public class Test
    {
    
        public Test()
        {
        }
    
        public static void main(String args[])
        {
            ArrayList arraylist = new ArrayList();
            arraylist.add("1");
            arraylist.add("2");
            Iterator iterator = arraylist.iterator();
            do
            {
                if(!iterator.hasNext())
                    break;        // 1
                String s = (String)iterator.next();         // 2
                if("2".equals(s))
                    arraylist.remove(s);
            } while(true);
            System.out.println((new StringBuilder()).append("list:").append(arraylist).toString());
        }
    }
    
    

    通过这个反编译结果我们可以看到foreach底层其实还是使用iterator进行迭代。并且Debug上面的代码,发现当删除"2"元素后,代码执行到2处时报错;但当删除"1"元素后,代码会执行1处代码退出循环,由于没有执行2处的代码,所以删除"1元素"时不会报错。那么有人可能就会问了:为什么删除"2"元素后,1处代码不执行?我们可以通过查看ArrayList的hasNext()的源码找到答案:

    
    class ArrayList {
        private int size;   // The size of the ArrayList (the number of elements it contains).
    
        private class Itr implements Iterator {
            int cursor;       // index of next element to return
    	
    	public boolean hasNext() {
    	    return cursor != size;
    	}
        }
    	
    }
    
    

    当删除"1"元素后,cursor值为1("2"元素的下标),size值也为1,两者相等,故hasNext()返回false,所以执行1处代码;但当删除"2"元素后,cursor值为0("1"元素的下标),size值还是为1,两者不相等,故hasNext()返回true,所以无法执行1处代码。故而就出现了上面截然不同的结果。

    remove/add方法?

    那么为什么在执行2处代码时会报错呢?通过上面的报错信息我们可以看出,ConcurrentModificationException是在执行checkForComodification()的过程抛出的,checkForComodification()的源码如下所示:

    
    final void checkForComodification() {
        if (modCount != expectedModCount)   
            throw new ConcurrentModificationException();
    }
    
    

    上面方法中的modCount是ArrayList中的一个成员变量,它表示该集合实际被修改的次数expectedModCount是ArrayList中的一个内部类Itr(Itr是一个Iterator的实现:使用ArrayList.iterator()获取到的迭代器就是Itr类的实例)中的成员变量,它表示这个迭代器期望该集合被修改的次数,需要注意的是这个值是在集合调用iterator()时初始化,并且只有通过该迭代器对集合进行操作时,该值才会发生改变。

    那么remove()/add()为什么会导致这两者的值不等呢?它对集合中的元素是怎样进行操作的呢?查看remove方法的源码:

    
    public E remove(int index) {
        rangeCheck(index);
        checkForComodification();
        E result = parent.remove(parentOffset + index);
        this.modCount = parent.modCount;       
        this.size--;
        return result;
    }
    
    

    由上面的源代码我们可以看到remove()仅对modCount变量进行了操作。于是我们就可以知道:在foreach(即iterator)对集合进行遍历时,元素在"自己"不知不觉的情况下被删除/添加,这时就会抛出异常,提示用户可能发生了并发修改。

    如何解决?

    明白了为什么之后,我们就需要思考如何去解决它:

    • 使用Iterator提供的remove()。
    
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    
    public class Test {
        public static void main(String[] args) {
            List<String> list = new ArrayList<String>();
            list.add("1");
            list.add("2");
    
            Iterator iterator = list.iterator();
            while(iterator.hasNext()) {
                if("2".equals(iterator.next())) {
                    iterator.remove();
                }
            }
    
            System.out.println("list:" + list);
        }
    }
    
    
    • 如果是List集合,还可以使用listIterator提供的remove()和add()。
    
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    
    public class Test {
        public static void main(String[] args) {
            List<String> list = new ArrayList<String>();
            list.add("1");
            list.add("2");
    
    
            Iterator iterator = list.listIterator();
            while(iterator.hasNext()) {
                if("2".equals(iterator.next())) {
                    iterator.remove();
    //                ((ListIterator) iterator).add("3");
                }
            }
    
            System.out.println("list:" + list);
        }
    }
    
    
    • 使用Java8提供的filter进行过滤:在Java8中可以把集合转换成流,并且对于流有一种filter操作,它可以对原始Stream进行某项过滤,通过过滤的元素被留下来生成一个新Stream。
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.stream.Collectors;
    
    public class Test {
        public static void main(String[] args) {
            List<String> list = new ArrayList<String>() {{
                add("1");
                add("2");
            }};
    
            list = list.stream().filter(name -> ! name.equals("2")).collect(Collectors.toList());
            System.out.println("list:" + list);
        }
    }
    
    
  • 相关阅读:
    jQuery杂项方法
    nodeJS实现路由功能
    nodeJS之HTTP
    nodeJS之TCP模块net
    nodeJS之事件events
    NodeJS之queryString
    chrome谷歌浏览器用这种方式清除缓存比较方便了,必须是调试模式才行
    DeepMind已将AlphaGo引入多领域 Al泡沫严重
    学习Linux的正确姿势
    现在90,00后经常上哪些网站?喜欢看啥网站?
  • 原文地址:https://www.cnblogs.com/syhyfh/p/12551092.html
Copyright © 2020-2023  润新知