谈谈使用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)。因此,如果我们想要移除某个值,且这个值是数值的时候,我们需要注意一下这个问题。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章