본문 바로가기

Java

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

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

Iterator 인터페이스

1
2
3
4
5
6
7
8
9
10
11
12
13
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 인터페이스를 구현한 클래스는

순회를 할 수 있다.

 

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

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

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

 

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

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

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 메소드가 추가된 덕분에 

 

1
2
List<Integer> numbers = Arrays.asList(12345);
  numbers.stream().forEach(System.out::println);
 

 

위처럼 stream으로 forEach 를 하는 것이 아니라

 

1
2
List<Integer> numbers = Arrays.asList(12345);
  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 메소드를 재정의한것을 보면

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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를 어떻게 사용할 수 있을까? 라는

궁금증이 생겼다.

 

사실 쓸데없는 궁금증이지만

궁금해진 김에 끝을 보고 싶어서

이리저리 검색하고 찾아봤지만

명쾌한 답을 얻지는 못했다.

 

그래서 OKKY에 질문 글을 올렸다.

 

 

향상된 for 구문이 적절하게 번역을 해준다는 사실은 알게 되었지만

첨부해주신 링크에 들어가 봐도

어떻게 번역을 해준다는 것인지..

나의 궁금증은 명쾌하게 해결되지 않았다.

 

결국 크루들에게 나의 궁금증을 공유했고

겨우 답을 얻을 수 있었다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Test {
 
    public static void main(String[] args) {
        Car[] cars = {new Car("둔덩"), new Car("카일")};
        for (Car car : cars) {
            System.out.println(car.name);
        }
    }
 
}
 
class Car{
    public String name;
 
    public Car(String name) {
        this.name = name;
    }
}
 
 

다음과 같은 Test.java 가 있을 때

Test.java 를 컴파일러가 번역한 Test.class 를 살펴보면

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test {
    public Test() {
    }
 
    public static void main(String[] args) {
        Car[] cars = new Car[]{new Car("둔덩"), new Car("카일")};
        Car[] var2 = cars;
        int var3 = cars.length;
 
        for(int var4 = 0; var4 < var3; ++var4) {
            Car car = var2[var4];
            System.out.println(car.name);
        }
 
    }
}
r
 

 

위의 코드처럼 for-each loop를 일반 fo r문으로 컴파일러가 번역한 것을 알 수 있다.

 

결론적으로

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

iterable 을 구현하지 않은 객체가 for-each loop를 사용하면

자바 컴파일러가 for-each loop를 for loop로 적절하게 번역을 해준다는 사실.

 

 

참고문서

http://wonwoo.ml/index.php/post/1812

https://m.blog.naver.com/PostView.nhn?blogId=writer0713&logNo=220877874725&proxyReferer=https%3A%2F%2Fwww.google.com%2F

https://wedul.site/459

https://loco-motive.tistory.com/63

https://92bluemoon.netlify.com/posts/iterator-iterable/