Skip to content

[MEJ-013] 자바 Collections로 Iterator 더 깊게 살펴보기 #220

@glenn-syj

Description

@glenn-syj

based on: #215 by @yngbao97

들어가며

Java에서 반복문을 이용할 때 for문, for-each문, while문은 익숙할 만큼 자주 사용하게 됩니다. 그러나 @yngbao97 님의 글에서도 언급되었듯이 Iterator 자체가 익숙하지는 않습니다. 사실 IteratorIterable 인터페이스가 Java Collections에서 중요한 역할을 담당하고 있음에도 말입니다. 따라서 이번 글에서는, Java Collection에서 Iterator를 어떻게 지원하는지 ArrayList 클래스를 통해 살펴보겠습니다.

ArrayList가 Iterator를 지원하는 방식

ArrayList는 AbstractList 추상 클래스를 상속받으며, AbstractList에 이미 존재하는 Iterator 인터페이스 구현 클래스 Itr이나 ListItr를 이용하지 않습니다. ArrayList 자체적으로 내부 클래스 Itr 또는 ListItr를 만들어서 이용하는데요.

Iterator 구현체 Itr

private class Itr implements Iterator<E> {
      int cursor;       // index of next element to return
      int lastRet = -1; // index of last element returned; -1 if no such
      int expectedModCount = modCount;

      // prevent creating a synthetic constructor
      Itr() {}

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

      @SuppressWarnings("unchecked")
      public E next() {
          checkForComodification();
          int i = cursor;
          if (i >= size)
              throw new NoSuchElementException();
          Object[] elementData = ArrayList.this.elementData;
          if (i >= elementData.length)
              throw new ConcurrentModificationException();
          cursor = i + 1;
          return (E) elementData[lastRet = i];
      }

      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
      public void forEachRemaining(Consumer<? super E> action) {
          Objects.requireNonNull(action);
          final int size = ArrayList.this.size;
          int i = cursor;
          if (i < size) {
              final Object[] es = elementData;
              if (i >= es.length)
                  throw new ConcurrentModificationException();
              for (; i < size && modCount == expectedModCount; i++)
                  action.accept(elementAt(es, i));
              // update once at end to reduce heap write traffic
              cursor = i;
              lastRet = i - 1;
              checkForComodification();
          }
      }

      final void checkForComodification() {
          if (modCount != expectedModCount)
              throw new ConcurrentModificationException();
      }
  }

Itr 클래스는 단방향(인덱스가 적은 곳에서 큰 곳으로)으로의 순회를 지원합니다. 즉, 순차적으로 탐색할 때 이용됩니다. 그런데 코드에서도 볼 수 있듯이, 삭제는 가능하지만 수정은 불가능합니다. 양방향 탐색도 불가능합니다.

특히, 삭제를 담당하는 remove() 메서드는 현재 커서가 있는 곳이 아니라 이전 lastRet 위치의 요소를 삭제하고, 커서를 lastRet 인덱스에 둡니다. 이러한 방식으로 안전하게 요소를 삭제할 수 있습니다. 동시성에서 비롯되는 예외처리를 한 것도 인상적입니다. checkForModification()modCount를 이용해 외부에서 수정이 일어났는지 확인하는 메서드입니다.

ListIterator 구현체 ListItr

/**
     * An optimized version of AbstractList.ListItr
     */
    private class ListItr extends Itr implements ListIterator<E> {
        ListItr(int index) {
            super();
            cursor = index;
        }

        public boolean hasPrevious() {
            return cursor != 0;
        }

        public int nextIndex() {
            return cursor;
        }

        public int previousIndex() {
            return cursor - 1;
        }

        @SuppressWarnings("unchecked")
        public E previous() {
            checkForComodification();
            int i = cursor - 1;
            if (i < 0)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i;
            return (E) elementData[lastRet = i];
        }

        public void set(E e) {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.set(lastRet, e);
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        public void add(E e) {
            checkForComodification();

            try {
                int i = cursor;
                ArrayList.this.add(i, e);
                cursor = i + 1;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
    }

ListItrListIterator의 구현체입니다. 여기에서는 양방향 탐색과 함께 요소의 수정, 추가, 삭제가 모두 지원됩니다. 게다가 이전 인덱스 역시 조회할 수 있습니다.

AbstractList 내부 Iterator와의 차이

AbstractList 내부의 Itr 클래스는 get() 메서드를 이용해 요소를 반환하는 반면, ArrayList 내부의 Itr 클래스는 배열에 직접 접근해 요소를 반환합니다. 이는 당연하게도, ArrayList 클래스의 특성 상 배열로 참조해 더 빠르게 접근할 수 있는 까닭입니다. 따라서 O(1)의 시간 복잡도로 접근할 수 있습니다.

Metadata

Metadata

Assignees

Labels

weekly reviewsReview for the docs of others

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions