본문 바로가기

1. Iterator

 

1.1 Iterator의 정의

 

컬렉션의 데이터를 얻어오는데 사용하는 인터페이스이다. 컬렉션 구현체의 `iterator()`메서드를 통해서 Iterator를 얻을 수 있다. 아래코드는 Iterater인터페이스이다. 

public interface Iterator<E> 
	boolean hasNext();
	E next();
    default void remove() {
        throw new UnsupportedOperationException("remove");
    }
    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
}

 

 

2. ArrayList에서 구현한 Iterator의 동작원리

ArrayList에서 구현되어 있는 Iterator를 통해서 원리를 파악해보자.  기본적으로 `cursor`, `lastRet`, `expectedModCount`라는 상태를 가지고 있다. `cursor`는 현재 가리키고있는 인덱스값이고 `lastRet`은 가장 최근에 iterator에서 반환된 요소의 인덱스이다.

 

2.1 Iterator가 가지고 있는 상태

 

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;
}

2.2 Iterator의 hasNext()

구현된 `hasNext()`는 아래와같다. ArrayList의 사이즈와 cursor가 다르면 다음값이 있다는것으로 판단하고 true나 false를 반환한다.

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

2.3 Iterator의 next()

  `next()`에 대해서 알아보자. 기본원리는 lastRet은 현재인덱스를 가리키도록하고 cursor는 앞으로 한칸 전진시킨다. 그리고 lastRet이 가리키는 요소를 반환한다.

 cursor를 전진하기전에 `checkForComodification()`이라는 메서드를 호출한다. 동시성문제를 피하기 위해서 확인하는 메서드인데 modCount와 expectedModCount값이 서로 다르면 exception을 던져주는 메서드이다. `modCount`는 `AbstractList`의 상태값이다. list에 새로운 값을 추가할때 1씩 증가시키고 제거할때도 1씩 증가시킨다.

 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];
 }
 
 final void checkForComodification() {
     if (modCount != expectedModCount)
           throw new ConcurrentModificationException();
 }

 

아래코드는 아무 문제가 일어날것같지 않지만 ConcurrentModificationException이 발생한다. for문을 돌기전에 add를 3회했기때문에 modCount는 3이되고  반복문을 foreach문으로 반목문을 수행하면서 최초에 iterator를통해서 String값을 꺼내온다 이시점에 iterator객체를 생성하는데 modCount는 3이된다. 그리고 remove를하면서 modCount는 4가 된다. 하지만 iterator를 생성할때 iterator의 상태로 가지고 있던 expectedModCount는 3이므로 expectedModCount와 modCount값이 다르므로 수정을하지 못하게 exception을  발생해준다.

final List<String> list = new ArrayList<String>();

list.add("A");
list.add("B");
list.add("C");

for(String s : list) {
	if(s.equals("A")) {
   		list.remove(s);
	}
}

 

checkForComodification()을 종료하고나면 list의 사이즈에대한 예외상황을 처리하고나서 cursor를 1증가시켜주고 lastRest에 cursor의 값을 넣어서 다음 요소를 반환시켜준다.

 

2.4 Iterator의 remove()

next()와 마찬가지고 checkForComodifiaction()함수를 호출한다. lastRet은 가장 최근에 반환된 요소의 인덱스가 들어있다. 즉, remove()호출하면 lastRet 인덱스위치에 있는 list의 요소를 삭제하고 cursor가 lastRet을 가리키도록 하고 lastRet은 -1을 넣는다. (-1은 가장 최근에 반환된 요소가 없음을 의미) 

 

 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();
     }
 }
list.add("A");
list.add("B");
list.add("C");
Iterator iterator = list.iterator(); 
iterator.next(); //cursor = 1,lastRet = 0이 되고 lastRet=0요소인 A를 반환한다.
iterator.remove(); // 0번째 요소인 A를 삭제한다.

 

 

3. iterator vs foreach

 

일단 iterator,foreach는 for문보다 성능이 좋다. 즉, 특정인덱스의 값이 필요한경우가 아니라면 foreach를 사용하는것이 좋다. 그리고 컬렉션에서 element를 삭제해야하는 경우에 foreach문을사용하면 exception이 발생할 수 있다. 컬렉션을 삭제해야하는 경우에는 iterator를 통해서 삭제하는것이 좋다. 

 

 

  • for문 보다는 foreach문을
  • collection에서 element를 삭제해야한다면 iterator를
  • iterator와 foreach의 성능차이는 거의 없음

댓글