談談使用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)。因此,如果我們想要移除某個值,且這個值是數值的時候,我們需要注意一下這個問題。

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