일문일답/Java

[일문일답][Java] Iterator 인터페이스와 Iterable 인터페이스

HGLee-- 2022. 8. 25. 10:59

Iterator 인터페이스

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

Iterator 인터페이스는 위와 같은 메소드를 가진다.

remove 메소드와 forEachRemaining 메소드는 디폴트 메소드이고,

Iterator 인터페이스를 구현하고자 하는 클래스는 hasNext 메소드와 next 메소드를 override하면 된다.

 

Iterator를 사용한 디자인 패턴인 Iterator 패턴이 있다.

 

Iterator 패턴에서는 컬렉션 구현 방법을 노출시키지 않으면서도

그 집합체 안에 들어있는 모든 항목에 접근할 수 있는 방법을 제공해야 한다는 의미가 있다.

 

전에는 반복 횟수를 담당하는 클래스가 있을 때

Iterator 인터페이스를 구현해서 클래스 스스로를 반복자의 역할로 사용하면 좋다고 생각했었는데 잘못된 것이였다.

 

다른 개발자가 Iterator 인터페이스가 구현된 걸 본다면

위에서 말했듯이

컬렉션 구현 방법을 노출시키지 않으면서도

그 집합체 안에 들어있는 모든 항목에 접근할 수 있는 방법을 제공해야 한다는 의미를 떠올릴 것이다.

그런데 나는 반복자 역할로 구현했다면

디자인 패턴 장점 중 하나인 개발자간의 의사소통을 원활하게 하고

상호간 의사결정의 용어로 쓰이는 관점에서 본다면  문제가 될 수 있다.

 

결론적으로

Iterator를 구현하는 방법은

컬렉션 구현 방법을 노출시키지 않으면서도

그 집합체 안에 들어있는 모든 항목에 접근할 수 있는 방법을 제공해야 할 때 사용하자.

 

 

Iterable 인터페이스

Iterable 인터페이스는 그 이름처럼 Iterable 인터페이스를 구현한 클래스는

순회를 할 수 있다.

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
 
for (Integer i : numbers) {
    System.out.println(i);
}
 

즉, 위 코드처럼 for-each loop를 사용할 수 있게 된다.

for-each loop = Enhanced For Loop = 향상된 for 문

 

Iterable 인터페이스는 최상위 인터페이스로 

Collection 인터페이스에서 상속하고 있다.

public interface Iterable<T> {
   Iterator<T> iterator();
    
   default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
 
   default Spliterator<T> spliterator() {
       return Spliterators.spliteratorUnknownSize(iterator(), 0);
   }
}
 

내부를 살펴보면 디폴트 메소드인 forEach 메소드와 spliterator 메소드를 가지고 있고

추상 메소드로 iterator 메소드를 가지고 있다.

 

Spliterator 인터페이스는 기존 존재했던 반복자인 Iterator 와 비슷하지만

자바 8에서 추가된 병렬 작업에 특화된 인터페이스이고

forEach 메소드는 자바 8부터 추가된 Iterable의 디폴트 메소드이다.

 

forEach 메소드가 추가된 덕분에 

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.stream().forEach(System.out::println);
 위처럼 stream으로 forEach 를 하는 것이 아니라
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.forEach(System.out::println);

stream을 생성하는 추가 비용 없이 List에서 바로 forEach를 할 수 있게 되었다.

 

위의 글에서도 알 수 있듯이

Collection은 최상위 인터페이스인 Iterable을 상속하고 있다.

그 때문에 List에서도 Iterable의 디폴트 메소드인 forEach를 사용할 수 있는 것이다.

 

 

중요한 부분은 추상 메소드인 Iterator<T> iterator(); 이다.

 

Iterable 인터페이스의 중요한 부분은 

Iterable 을 구현한 클래스는 iterator()를 사용하여

Iterator 인터페이스를 반환 할 수 있고

 

iterator() 메소드를 통해 for-each loop를 사용할 수 있다는 점이다.

 

for-each loop가 내부적으로 iterator() 메소드를 객체에 호출하는 로직이기 때문이다.

(인텔리 제이에서 iter을 입력한 후 엔터를 치면 향상된 for 문이 나온다)

 

결론적으로, Iterable을 구현한 객체에서만 for-each loop를 사용할 수 있다.

 

 

Iterable 을 구현한 클래스에서는

반드시 iterator() 메소드를 override해야 한다.

 

앞서 말했듯이 Collection인터페이스에서는 Iterable 을 상속하고 있기 때문에

Collection 인터페이스를 상속하는 List, Queue, Set 인터페이스 등등에서도 Iterable을 상속하고 있고

List, Queue, Set  인터페이스 등을 구현하는 ArrayList, LinkedList 클래스 등에서는

Iterable의 Iterator 메소드를 override하고 있다.

 

때문에 우리가 Collection을 쓰면서 for-each-loop를 사용할 수 있는 것이다.

 

ArrayList에서 iterator 메소드를 재정의한것을 보면

 

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    
    public Iterator<E> iterator() {
            return new 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;
 
    Itr() {}
 
    public boolean hasNext() {
        return cursor != size;
    }
 
    @SuppressWarnings("unchecked")
    public E next() {          
     ...
    }
     ... 
}​
 

Iterator 인터페이스를 구현한 Itr 클래스를 리턴하고 있는 것을 볼 수 있다.

(Iterator 인터페이스의 hasNext()와 next()를 성실히 재정의했다.)

 

for-each loop는 iterator() 메소드를 객체에 호출하는 오버헤드가 있기 때문에 기존의 for 문 보다 속도가 느리지만, 그 차이는 미미하다.

 

공부를 하던 중, Collection을 사용하지 않고 일반 배열로 Iterable 을 구현하지 않은 객체를 담았을 때 for-each loop를 사용할 수 있었는데,

 

Iterable을 구현하지도 않은 객체에서 iterator() 메소드를 사용해서 반복하는 for-each loop를 어떻게 사용할 수 있을까? 라는 궁금증이 생겼다.

 

// .java
@Test
void iterableTest() {
    Integer[] arr = new Integer[]{1, 2, 3, 4, 5};
    for (Integer integer : arr) {
        System.out.println(integer);
    }

    List<Integer> list = Arrays.asList(arr);
    for (Integer integer : list) {
        System.out.println(integer);
    }
}


// .class
@Test
void iterableTest() {
    Integer[] arr = new Integer[]{1, 2, 3, 4, 5};
    Integer[] var2 = arr;
    int var3 = arr.length;

    for(int var4 = 0; var4 < var3; ++var4) {
        Integer integer = var2[var4];
        System.out.println(integer);
    }

    List<Integer> list = Arrays.asList(arr);
    Iterator var7 = list.iterator();

    while(var7.hasNext()) {
        Integer integer = (Integer)var7.next();
        System.out.println(integer);
    }
}

위의 코드에서 볼 수 있듯, 일반 배열에서의 for-each loop를 컴파일러가 일반 for문으로 번역한 것을 확인할 수 있다.

 

결론적으로,

iterable 인터페이스를 구현한 객체만이 for-each loop를 사용할 수 있는데,

iterable 을 구현하지 않은 객체가 for-each loop를 사용하면 자바 컴파일러가 적절하게 for-each loop를 for loop로 번역을 해준다는 것을 알 수 있다.

 

 

[출처]

https://dundung.tistory.com/170

 

Iterator 인터페이스와 Iterable 인터페이스

우테코에서 자동차 경주 미션을 진행하다가 for 문을 반드시 사용해야 하는 곳에 Iterator 인터페이스를 구현하여 for 문 없이(내부적으로는.. 사용) 간략하게 구현한 한 크루의 코드를 보고 Iterator

dundung.tistory.com