과정을 즐기자

Java의 foreach 문의 내부 동작 방식 (feat. Iterator, Iterable) 본문

Java

Java의 foreach 문의 내부 동작 방식 (feat. Iterator, Iterable)

320Hwany 2023. 9. 24. 13:33

자바의 for 문에는 일반 for 문과 foreach (향상된 for 문)이 있습니다.

일반 for 문

for (int i = 0; i < 10; i++) {
    System.out.println(members[i]);
}

foreach 문

for (Member member : members) {
    System.out.println(member);
}

지금까지 foreach 문이 간결하고 가독성도 좋기 때문에 자주 사용했습니다.

하지만 foreach 문은 언제 사용할 수 있고 내부 동작 방식에 대해서 의문점이 생겼습니다.

이번 글에서는 foreach 문은 어떤 내부 동작 방식으로 인해 어떠한 조건일 때 사용할 수 있는 지에 대해 알아보겠습니다.

 

먼저 Iterator, Iterable 인터페이스에 대해 알아보겠습니다.

Iterator, Iterable 인터페이스

Iterator 인터페이스는 Collection에 저장된 요소를 읽어오는 것을 표준화한 인터페이스입니다.

Java에서 구현한 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());
    }
}

hasNext(), next()는 Iterator 구현시 직접 구현해야 하는 메소드이며 remove(), forEachRemaining은 default 메소드로

구현되어 있습니다.

 

Iterable 인터페이스는 하위 클래스에 Iterator를 반환하는 iterator() 메소드의 구현을 강제하는 역할을 합니다.

Java에서 구현한 Iterable을 살펴보겠습니다.

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

Iterator를 반환하는 iterator() 메소드를 구현해야 하며 forEach(), spliterator()는 default 메소드로 구현되어 있습니다.

foreach는 어떤 조건에 사용할 수 있나?

이제 Iterator, Iterable의 구현체를 직접 구현해보며 foreach를 어떤 조건에 사용할 수 있는 지 알아보겠습니다.

public class MyCollections<T> implements Iterable<T> {

    private final T[] elements;

    public MyCollections(T[] elements) {
        this.elements = elements;
    }

    @Override
    public Iterator<T> iterator() {
        return new MyIterator();
    }

    private class MyIterator implements Iterator<T> {

        private int index = 0;

        @Override
        public boolean hasNext() {
            return index < elements.length;
        }

        @Override
        public T next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            return elements[index++];
        }
    }
}

Iterable 인터페이스를 구현하여 MyCollections 컬렉션을 구현하였고 iterator() 메소드를 필수적으로 구현해야 합니다.

iterator() 메소드를 구현하기 위해 MyIterator 클래스를 생성하고 다음 원소가 있는지 확인하는 hasNext(), 다음 원소를

반환하는 next() 메소드를 구현하였습니다.

 

@Test
@DisplayName("foreach를 사용하려면 Iterable을 구현해야 합니다")
void test() {
    // given
    Member member1 = new Member("이름1", 20);
    Member member2 = new Member("이름2", 20);
    Member member3 = new Member("이름3", 20);
    Member member4 = new Member("이름4", 20);
    Member member5 = new Member("이름5", 20);

    Member[] members = new Member[]{member1, member2, member3, member4, member5};

    // when
    MyCollections<Member> myCollections = new MyCollections<>(members);

    // then
    for (Member memberCollection : myCollections) {
        System.out.println(memberCollection.getName());
    }
}

MyCollections는 foreach 문을 사용할 수 있습니다. 

이렇게 사용할 수 있는 이유는 MyCollections은 Iterable을 구현하였고 Iterable은 Iterator를 반환하는 메소드가 있습니다.

Iterator에는 hasNext(), next() 메소드를 구현해 Collection에 저장된 요소를 읽어오는 것을 표준화 하였습니다.

내부적으로 Iterator에 구현된 메소드를 이용해서 foreach로 간결하게 표현할 수 있는 것입니다.

// foreach 문
for (Member memberCollection : myCollections) {
    System.out.println(memberCollection.getName());
}

// foreach 문의 내부 동작 과정
Iterator<Member> iterator = myCollections.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}

 

실제로 Java의 Collection 인터페이스는 Iterable 인터페이스를 상속받기 때문에 iterator() 메소드의 구현이 강제되어

foreach 문으로 사용할 수 있습니다.

public interface Collection<E> extends Iterable<E> {

    ...
  
    Iterator<E> iterator();
    
    ...

}

정리

Iterator 인터페이스는 Collection에 저장된 요소를 읽어오는 것을 표준화하였고

Iterable 인터페이스는 하위 클래스에 Iterator를 반환하는 iterator() 메소드의 구현을 강제하는 역할을 합니다.

이 2가지 인터페이스를 이용해서 내부적으로 hasNext(), next() 메소드를 이용해서 간결하고 가독성이 좋은

foreach 문을 작성할 수 있습니다.

자바의 Collection은 Iterable를 상속받기 때문에 foreach 문을 사용할 수 있으며 직접 구현한 클래스에도 foreach 문을

사용하려고 한다면 Iterable, Iterator 인터페이스의 구현이 필요합니다.

 

참고한 자료

 

[Java] Iterable 과 Iterator 이란?

Collection framework는 뭔가 되게 많고 복잡한 느낌이 들어서 완벽하게 정리가 된 느낌은 아니었다. 가령 Iterator는 어떤 역할인지는 알겠는데 어떤 계층구조를 갖고 있는지 궁금했고, 공부하다보니 It

devlog-wjdrbs96.tistory.com