1. 程式人生 > >Java8,iterator的forEachRemaining中remove可能拋異常

Java8,iterator的forEachRemaining中remove可能拋異常

Java8的官方文件中,對於iterator的forEachRemaining的用法介紹如下:

default void forEachRemaining(Consumer<? super E> action)
Performs the given action for each remaining element until all elements have been processed or the action throws an exception. Actions are performed in the order of iteration, if that order is specified. Exceptions thrown by the action are relayed to the caller.
Implementation Requirements:
The default implementation behaves as if:

     while (hasNext())
         action.accept(next());
詳見:https://docs.oracle.com/javase/8/docs/api/java/util/Iterator.html

如果按照文件上的理解似乎可以在forEachRemaining的過程中remove元素:

ArrayList<String> list = new ArrayList();
        for (int i = 0; i < 10; i++) {
            list.add(String.valueOf(i));
        }

        Iterator iterator = list.iterator();
        iterator.forEachRemaining(new Consumer() {
            @Override
            public void accept(Object o) {
                System.out.println(o);
                  if (o.equals("3") ) {
                      System.out.println("remove");
                      iterator.remove();
                  }
            }
        });
但是,卻會報異常java.lang.IllegalStateException,和文件描述不一樣。其實是因為在ArrayList中,介面Iterator的forEachRemaining方法被覆蓋時,和介面文件描述不一致導致的。
private class Itr implements Iterator<E> { //arraylist的實現
    int cursor;       // index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such
    int expectedModCount = modCount;

    public boolean hasNext() {
        return cursor != size;
    }


    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public void forEachRemaining(Consumer<? super E> consumer) {
        Objects.requireNonNull(consumer);
        final int size = ArrayList.this.size;
        int i = cursor;
        if (i >= size) {
            return;
        }
        final Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length) {
            throw new ConcurrentModificationException();
        }
        while (i != size && modCount == expectedModCount) {
            consumer.accept((E) elementData[i++]);
        }
        // update once at end of iteration to reduce heap write traffic
        cursor = i;
        lastRet = i - 1; //只有所有acconsumer都執行完時,才會調整lastRest的值
        checkForComodification();
    }

 }

原因如下:

①在ArrayList內的Iterator中,remove基於lastRet。

②next()後,lastRest屬性會賦值為剛剛獲取的元素的index。

③在remove後,lastRet會被置為-1,而且remove(),會校驗lastRet值是否>=0,所以導致了一次next(),只能remove一次。

④而呼叫forEachRemaining就會遇到問題了,它在迴圈執行consumer的過程中,並非呼叫next(),而是手動的去向後遍歷,在這個過程中並未將lastRet置為剛剛獲取物件的index。只有遍歷到最後一個以後,才會修改lastRet值。

所以一旦在cousumer中呼叫remove(),就有可能遇到lastRe為-1的情況,觸發java.lang.IllegalStateException。