美文网首页
使用迭代器遇到的小坑

使用迭代器遇到的小坑

作者: 粪球滚屎壳螂 | 来源:发表于2018-07-09 00:17 被阅读0次

    实际遇到的时候是去除集合中满足指定条件的元素。
    下面稍微修改了一下,写了一个例子,作为记录。
    错误的示例如下:

    //过滤集合中的偶数
        public static void main(String[] args) {
            print(initSet());
        }
    
        public static void print(HashSet<Integer> targetSet) {
            if (targetSet.size() > 0) {
                Iterator iterator = targetSet.iterator();
                while (iterator.hasNext()) {
                    Integer tempTarget = (Integer) iterator.next();
                    if (isEven(tempTarget)) {
                        targetSet.remove(tempTarget); //从集合中移除
                    }
                }
            }
            System.out.println(targetSet);
        }
    
        public static HashSet<Integer> initSet() {
            HashSet<Integer> targetSet = new HashSet<Integer>();
            targetSet.add(1);
            targetSet.add(2);
            targetSet.add(3);
            targetSet.add(4);
            targetSet.add(5);
            targetSet.add(6);
            targetSet.add(7);
            return targetSet;
        }
    
        //判断是不是偶数
        public static boolean isEven(int target) {
            if (target % 2 != 0) {
                return false;
            }
            return true;
        }
    

    当移除了第一个偶数之后,进行下一次迭代的时候 会有异常 java.util.ConcurrentModificationException 抛出,这个意义可以理解为当前集合的大小(长度)与迭代器所预计的大小(长度)有差错。根据字面意思解决这个问题需要在进行下一次迭代的时候修正这一种差错以保证迭代的长度和原有集合的长度一样,这样就能保证程序正常运行。
    此时想到了迭代器自带的remove()方法。在移除当前遍历到的对象,的同时也修改迭代器。于是修改代码为:

    //过滤集合中的偶数
        public static void main(String[] args) {
            print(initSet());
        }
    
        public static void print(HashSet<Integer> targetSet) {
            if (targetSet.size() > 0) {
                Iterator iterator = targetSet.iterator();
                while (iterator.hasNext()) {
                    Integer tempTarget = (Integer) iterator.next();
                    if (isEven(tempTarget)) {
                        targetSet.remove(tempTarget); //从集合中移除
                        iterator.remove(); //从迭代器中移除
                    }
                }
            }
            System.out.println(targetSet);
        }
    
        public static HashSet<Integer> initSet() {
            HashSet<Integer> targetSet = new HashSet<Integer>();
            targetSet.add(1);
            targetSet.add(2);
            targetSet.add(3);
            targetSet.add(4);
            targetSet.add(5);
            targetSet.add(6);
            targetSet.add(7);
            return targetSet;
        }
    
        //判断是不是偶数
        public static boolean isEven(int target) {
            if (target % 2 != 0) {
                return false;
            }
            return true;
        }
    

    此时发现还是有异常:java.util.ConcurrentModificationException。
    这时候就比较郁闷了,同时处理了原有集合,又处理了迭代器为什么还会有错呢?在看详细的异常记录 可以看到异常抛出的方法不一样:

    //第一个异常抛出于nextNode()方法
    Exception in thread "main" java.util.ConcurrentModificationException
        at java.util.HashMap$HashIterator.nextNode(HashMap.java:1442)
        at java.util.HashMap$KeyIterator.next(HashMap.java:1466)
        at com.learn.testclass.print(testclass.java:25)
        at com.learn.testclass.main(testclass.java:18)
    
    
    //第二个错误抛出于remove() 方法
    Exception in thread "main" java.util.ConcurrentModificationException
        at java.util.HashMap$HashIterator.remove(HashMap.java:1456)
        at com.learn.testclass.print(testclass.java:28)
        at com.learn.testclass.main(testclass.java:18)
    

    这个时候就可以开始找源码看具体的方法以及抛出异常的原因了。
    源码在HashIterator中,找不到的可以直接看异常找到,也可以顺着继承关系一步一步来找到,核心的方法下面贴出来

      abstract class HashIterator {
            Node<K,V> next;        // next entry to return
            Node<K,V> current;     // current entry
            int expectedModCount;  // for fast-fail
            int index;             // current slot
    
            HashIterator() {
                expectedModCount = modCount;
                Node<K,V>[] t = table;
                current = next = null;
                index = 0;
                if (t != null && size > 0) { // advance to first entry
                    do {} while (index < t.length && (next = t[index++]) == null);
                }
            }
    
            public final boolean hasNext() {
                return next != null;
            }
    
            final Node<K,V> nextNode() {
                Node<K,V>[] t;
                Node<K,V> e = next;
                if (modCount != expectedModCount)
                    throw new ConcurrentModificationException();
                if (e == null)
                    throw new NoSuchElementException();
                if ((next = (current = e).next) == null && (t = table) != null) {
                    do {} while (index < t.length && (next = t[index++]) == null);
                }
                return e;
            }
    
            public final void remove() {
                Node<K,V> p = current;
                if (p == null)
                    throw new IllegalStateException();
                if (modCount != expectedModCount)
                    throw new ConcurrentModificationException();
                current = null;
                K key = p.key;
                removeNode(hash(key), key, null, false, false);
                expectedModCount = modCount;
            }
        }
    

    从异常可以看到无论是nextNode() 方法还是remove()抛出的,我们可以定位到错误为判断

    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
    

    这里有一个关键参数 modCount 源码定义以及注释如下:

    /**
       * The number of times this HashMap has been structurally modified
       * Structural modifications are those that change the number of mappings in
       * the HashMap or otherwise modify its internal structure (e.g.,
       * rehash).  This field is used to make iterators on Collection-views of
       * the HashMap fail-fast.  (See ConcurrentModificationException).
       */
      transient int modCount;
    

    大概翻译出来就是这个参数用于记录整个集合被改变的次数。而整个代码中涉及修改这个值的都是对集合进行了结构上的改变 比如移除、新增、清空等,这些操作都会执行 modCount++ 或者 ++modCount 也就是说 只要改变了集合结构,那么modCount必然会被改变,而判断条件

    modCount != expectedModCount
    

    中 expectedModCount 是在创建迭代器的时候就直接获取了modCount的值。回到错误示例代码中

    //错误的例子1
    targetSet.remove(tempTarget); //从集合中移除
    // modCount 修改了而expectedModCount没修改
    
    //错误的例子2
    targetSet.remove(tempTarget); //从集合中移除
    //modCount 修改了而expectedModCount没修改
    iterator.remove(); //从迭代器中移除  remove中的判断出错
    

    此时可能想到了一个方法先从迭代器移除,不会异常,再从集合移除。

    iterator.remove(); //从迭代器中移除  
    targetSet.remove(tempTarget); //从集合中移除
    

    测试之后发现可行。
    至此问题是解决了,但是细细看看源码发现,其实在迭代器的remove()方法中,就已经包含了对集合的数据移除。也就是在目前的代码中

    targetSet.remove(tempTarget);
    

    是没用的,因为在运行这个代码的时候,实际的集合中,已经不存在 tempTarget 这个元素了,所以直接调用迭代器的remove ()就可以了。
    由此可修改为,正确的示例

    //过滤集合中的偶数
        public static void main(String[] args) {
            print(initSet());
        }
    
        public static void print(HashSet<Integer> targetSet) {
            if (targetSet.size() > 0) {
                Iterator iterator = targetSet.iterator();
                while (iterator.hasNext()) {
                    Integer tempTarget = (Integer) iterator.next();
                    if (isEven(tempTarget)) {
                        iterator.remove(); //从迭代器中同时移除。
                    }
                }
            }
            System.out.println(targetSet);
        }
    
        public static HashSet<Integer> initSet() {
            HashSet<Integer> targetSet = new HashSet<Integer>();
            targetSet.add(1);
            targetSet.add(2);
            targetSet.add(3);
            targetSet.add(4);
            targetSet.add(5);
            targetSet.add(6);
            targetSet.add(7);
            return targetSet;
        }
    
        //判断是不是偶数
        public static boolean isEven(int target) {
            if (target % 2 != 0) {
                return false;
            }
            return true;
        }
    

    以上为一个坑的记录,多看书,多看源码,少走弯路,谨记谨记。。。

    相关文章

      网友评论

          本文标题:使用迭代器遇到的小坑

          本文链接:https://www.haomeiwen.com/subject/iwibjftx.html