zoukankan      html  css  js  c++  java
  • 谈谈使用Iterator操作集合的时候踩的几个坑

    ConcurrentModificationException

    网上关于集合类型使用Iterator遍历需要注意的事项想必大家都已熟知,如果你想要遍历的时候删除集合中的元素,如果你像下面这样写,是会报错的!

        public void testRemove() {
            Iterator<String> iterator = list.iterator();
            while(iterator.hasNext()){
                String next = iterator.next();
                if("1".equals(next)){
                    list.remove(next);//引发ConcurrentModificationException
                }
            }
        }
    

    异常如下:

    Exception in thread "main" java.util.ConcurrentModificationException
    	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
    	at java.util.ArrayList$Itr.next(ArrayList.java:859)
    	at list.ListTest.testRemove(ListTest.java:40)
    	at list.ListTest.main(ListTest.java:33)
    

    Iterator迭代器采用fail-fast机制,一旦在迭代过程中检测到该集合已经被修改,程序会立即引发:ConcurrentModificationException异常,以避免共享资源而引发的潜在问题。

    正确的写法是使用iterator提供的remove方法:

        public void testCorrectRemove(){
            Iterator<String> iterator = list.iterator();
            while(iterator.hasNext()){
                String next = iterator.next();
                if("1".equals(next)){
                    iterator.remove(); // 使用迭代器的remove
                }
            }
            System.out.println(list); 
        }
    

    UnsupportedOperationException

    但是今天在测试的时候遇到一个问题,我的代码如下:

        public static void main(String[] args) {
    		// 注意这里创建list的方式
            List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
            Iterator<Integer> iterator = list.iterator();
            while (iterator.hasNext()) {
                Integer num = iterator.next();
                System.out.println(num);
                if (num == 2) {
                    iterator.remove();
                }
            }
            System.out.println(list);
        }
    

    引起了如下异常:

    Exception in thread "main" java.lang.UnsupportedOperationException
    	at java.util.AbstractList.remove(AbstractList.java:161)
    	at java.util.AbstractList$Itr.remove(AbstractList.java:374)
    	at list.ListTest.main(ListTest.java:28)
    

    这就奇怪了,为什么这个List不行呢?

        public static <T> List<T> asList(T... a) {
            return new ArrayList<>(a);
        }
    

    明明创建的就是一个ArrayList啊,事实上,此ArrayList非彼ArrayList,我们可以跟进去谈谈究竟:

        private static class ArrayList<E> extends AbstractList<E>
            implements RandomAccess, java.io.Serializable
        {
            private static final long serialVersionUID = -2764017481108945198L;
            private final E[] a;
    
            ArrayList(E[] array) {
                a = Objects.requireNonNull(array);
            }
        }
    

    这个ArrayList是定义在Arrays类中的一个静态内部类,和我平时使用的并不是同一个!并且,我们可以看看其中Iterator的remove方法:

            public void remove() {
                if (lastRet < 0)
                    throw new IllegalStateException();
                checkForComodification();
    
                try {
                    // 调用本来的remove方法
                    AbstractList.this.remove(lastRet);
                    if (lastRet < cursor)
                        cursor--;
                    lastRet = -1;
                    expectedModCount = modCount;
                } catch (IndexOutOfBoundsException e) {
                    throw new ConcurrentModificationException();
                }
            }
    

    !它调用了本类的remove方法,而remove方法并没有具体实现,而是抛出了异常:

        public E remove(int index) {
            throw new UnsupportedOperationException();
        }
    

    而我们平时的ArrayList的remove方法的实现是下面这个样子的:

        public E remove(int index) {
            rangeCheck(index);
    
            modCount++;
            E oldValue = elementData(index);
    
            int numMoved = size - index - 1;
            if (numMoved > 0)
                System.arraycopy(elementData, index+1, elementData, index,
                                 numMoved);
            elementData[--size] = null; // clear to let GC do its work
    
            return oldValue;
        }
    

    这下子,恍然大悟,原来是这样,了解这个之后,修改起来就相对简单了,我们把它转化为我们熟知的ArrayList就好了:

        public static void main(String[] args) {
    		// 转化为ArrayList
            List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
            Iterator<Integer> iterator = list.iterator();
            while (iterator.hasNext()) {
                Integer num = iterator.next();
                System.out.println(num);
                if (num == 2) {
                    iterator.remove();
                }
            }
            System.out.println(list);
        }
    

    移除指定数值

    先看个例子:

        public void testRemoveNumber() {
            List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
            list.remove(3);
            System.out.println(list);
        }
    

    如果程序执行,结果会是如何呢?答案如下:

    [1, 2, 3, 5]  // 移除了index = 3的元素,而不是移除值为3的元素
    

    如果我就是想移除值为3的元素呢?可以使用Integer包装一下。

    	public void testRemoveNumber() {
            List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
            list.remove(new Integer(3));
            System.out.println(list);
        }
    

    结果就变成了:

    [1, 2, 4, 5] // 移除了值为3元素
    

    两者区别在于:调用的remove方法不同。前者调用的是:remove(int index),后者调用的是remove(Object o)。因此,如果我们想要移除某个值,且这个值是数值的时候,我们需要注意一下这个问题。

  • 相关阅读:
    MongoDB Schema Design
    WinDBG中的poi是做什么用的?
    如何在Visual Studio中运行和调试汇编代码?
    [翻译图书] 未完工 Moving Applications to the Cloud on the Microsoft Windows Azure Platform 4
    在Word中生成随机的样本文本
    Quiz Win32内存表示与数值大小
    rep stos dword ptr es:[edi] 是做什么的?
    Windows Azure中虚拟机无法启动, 报错RoleStateUnknown的解决方案
    COM基础介绍
    64位的dump里如何寻找第一个到第四个参数?
  • 原文地址:https://www.cnblogs.com/summerday152/p/14128724.html
Copyright © 2011-2022 走看看