본문 바로가기

복습

[Java 복습] 직접 구현해보는 Iterator

목차

  • 순회란?
  • Iteraotor, Iterable
  • Iterable과 향상된 for문
  • 자바가 제공하는 Iterable, Iterator

 

순회

순회라는 단어는 여러 곳을 돌아다닌다는 뜻이다.
자료 구조에 순회는 자료 구조에 들어있는 데이터를 차례대로 접근해서 처리하는 것을 순회라 한다.
그런데 다양한 자료 구조가 있고, 각각의 자료 구조마다 데이터를 접근하는 방법이 모두 다르다.

 

예를 들어서 배열 리스트는 index 를 size 까지 차례로 증가하면서 순회해야 하고,

연결 리스트는 Node.next 를사용해node 의 끝이 null 일 때 까지 순회해야 한다.

이렇듯 각 자료 구조의 순회 방법이 서로 다르다.


배열 리스트, 연결 리스트, 해시 셋, 연결 해시 셋, 트리 셋 등등 다양한 자료 구조가 있다. 

각각의 자료 구조마다 순회하는 방법이 서로 다르기 때문에, 각 자료 구조의 순회 방법을 배워야 한다. 

그리고 순회 방법을 배우려면 자료 구조의 내부 구조도 알아야 한다. 

결과적으로 너무 많은 내용을 알아야 하는 것이다.

 

하지만 자료 구조를 사용하는 개발자 입장에서 보면 단순히 자료 구조에 들어있는 모든 데이터에 

순서대로 접근해서 출력하거나 계산하고 싶을 뿐이다.

 

자바는 이런 문제를 해결하기 위해 Iterable 과 Iterator 인터페이스를 제공한다.

 

 

 

Iterable, Iterator

  • Iterable : "반복 가능한"이라는 뜻이다.
  • Iterator : "반복자"라는 뜻이다.

 

Iterable 인터페이스의 주요 메서드

public interface Iterable<T> {
    Iterator<T> iterator();
}
  • 단순히 Iterator 반복자를 반환한다.

Iterator 인터페이스의 주요 메서드

public interface Iterator<E> {
    boolean hasNext();
    E next();
}
  • hasNext( ) : 다음 요소가 있는지 확인한다. 다음 요소가 없으면 false 를 반환한다.
  • next( ) : 다음 요소를 반환한다. 내부에 있는 위치를 다음으로 이동한다.

자료 구조에 들어있는 데이터를 처음부터 끝까지 순회하는 방법은 단순하다. 

자료 구조에 다음 요소가 있는지 물어보고, 있으면 다음 요소를 꺼내는 과정을 반복하면 된다. 

만약 다음 요소가 없다면 종료하면 된다. 이렇게 하면 자료 구조에 있는 모든 데이터를 순회할 수 있다.


Iterable , Iterator 를 사용하는 자료 구조를 하나 만들어보자. 둘 다 인터페이스여서 구현체가 필요하다.

 

먼저 Iterator 의 구현체를 만들자.

public class MyArrayIterator implements Iterator<Integer> {

    private int currentIndex = -1;
    private int[] targetArr;

    public MyArrayIterator(int[] targetArr) {
        this.targetArr = targetArr;
    }

    @Override
    public boolean hasNext() {
        return  currentIndex < targetArr.length - 1;
    }

    @Override
    public Integer next() {
        return targetArr[++currentIndex];
    }
}

 

Iterator 는 단독으로 사용할 수 없다. Iterator 를 통해 순회의 대상이 되는 자료 구조를 만들어보자.
여기서는 매우 간단한 자료 구조를 하나 만들자. 내부에는 숫자 배열을 보관한다.

public class MyArray implements Iterable<Integer>{

    private int[] numbers;

    public MyArray(int[] numbers) {
        this.numbers = numbers;
    }

    @Override
    public Iterator<Integer> iterator() {
        return new MyArrayIterator(numbers);
    }
}

 

기능 사용해보기

public class MyArrayMain {

    public static void main(String[] args) {

        MyArray MyArray = new MyArray(new int[]{1, 2, 3, 4});
        Iterator<Integer> iterator = MyArray.iterator();
        System.out.println("iterator 사용");
        while (iterator.hasNext()) {
            Integer value = iterator.next();
            System.out.println("value = " + value);
        }
    }
}
iterator 사용
value = 1
value = 2
value = 3
value = 4

 

 

클래스 구조도

 

 

 

MyArray 는 Iterable (반복할 수 있는) 인터페이스를 구현한다. 따라서 MyArray 는 반복할 수 있다는 의미가 된다.
Iterable 인터페이스를 구현하면 iterator() 메서드를 구현해야 한다. 이 메서드는 Iterator 인터페이스를 구현한 

반복자를 반환한다. 여기서는 MyArrayIterator 를 생성해서 반환했다.

 

 

런타임 메모리 구조도

 

  • MyArrayIterator 의 인스턴스를 생성할 때 순회할 대상을 지정해야 한다. 여기서는 MyArray 의 배열을 지정했다.
  • MyArrayIterator 인스턴스는 내부에서 MyArray 의 배열을 참조한다.
  • 이제 MyArrayIterator 를 통해 MyArray 가 가진 내부 데이터를 순회할 수 있다.

 

 

Iterable과 향상된 for문(Enhanced For Loop)

Iterable , Iterator 를 사용하면 또 하나의 큰 장점을 얻을 수 있다.

 

 

for-each문으로 불리는 향상된 for문은 자료 구조를 순회하는 것이 목적이다.
자바는 Iterable 인터페이스를 구현한 객체에 대해서 향상된 for문을 사용할 수 있게 해준다.

for (int value : myArray) {
    System.out.println("value = " + value);
}

 

이렇게 하면 자바는 컴파일 시점에 다음과 같이 코드를 변경한다.

while (iterator.hasNext()) {
    Integer value = iterator.next();
    System.out.println("value = " + value);
}

 

따라서 두 코드는 같은 코드이다. 물론 모든 데이터를 순회한다면 둘 중에 깔끔한 향상된 for문을 사용하는 것이 좋다.

 

 

자바가 제공하는 Iterable, Iterator

  • 자바 컬렉션 프레임워크는 배열 리스트, 연결 리스트, 해시 셋, 연결 해시 셋, 트리 셋 등등 다양한 자료 구조를 제공한다.
  • 자바는 컬렉션 프레임워크를 사용하는 개발자가 편리하고 일관된 방법으로 자료 구조를 순회할 수 있도록 Iterable 인터페이스를 제공하고, 이미 각각의 구현체에 맞는 Iterator 도 다 구현해두었다.
  • 자바 Collection 인터페이스의 상위에 Iterable 이 있다는 것은 모든 컬렉션을 Iterable 과Iterator 를 사용해서 순회할 수 있다는 뜻이다.
  • Map 의 경우 Key 뿐만 아니라 Value 까지 있기 때문에 바로 순회를 할 수는 없다. 
  • 대신에 Key 나 Value 를 정해서 순회할 수 있는데, keySet( ) , values( ) 를 호출하면 Set , Collection 을 반환하기 때문에 Key 나Value 를 정해서 순회할 수 있다. 
  • 물론 Entry 를 Set 구조로 반환하는 entrySet( ) 도 순회가 가능하다.

정리하면 자바가 제공하는 컬렉션 프레임워크의 모든 자료 구조는 Iterable 과 Iterator 를 사용해서 편리하고

일관된 방법으로 순회할 수 있다. 물론 Iterable 을 구현하기 때문에 향상된 for문도 사용할 수 있다.

public class JavaIterableMain {

    public static void main(String[] args) {

        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);

        Set<Integer> set = new HashSet<>();
        set.add(1);
        set.add(2);
        set.add(3);

        printAll(list.iterator());
        printAll(set.iterator());
        forEach(list);
        forEach(set);
    }

    private static void printAll(Iterator<Integer> iterator) {
        System.out.println("iterator.getClass() = " + iterator.getClass());
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }

    private static void forEach(Iterable<Integer> iterable) {
        System.out.println("iterable.getClass() = " + iterable.getClass());
        for (Integer integer : iterable) {
            System.out.println(integer);
        }
    }
}
iterator.getClass() = class java.util.ArrayList$Itr
1
2
3
iterator.getClass() = class java.util.HashMap$KeyIterator
1
2
3
iterable.getClass() = class java.util.ArrayList
1
2
3
iterable.getClass() = class java.util.HashSet
1
2
3
  • Iterator , Iterable 은 인터페이스이다. 따라서 다형성을 적극 활용할 수 있다.
  • printAll() , foreach() 메서드는 새로운 자료 구조가 추가되어도 해당 자료 구조가 Iterator , Iterable 만 구현하고 있다면 코드 변경 없이 사용할 수 있다.
  • java.util.ArrayList$Itr : ArrayList 의 Iterator 는 ArrayList 의 중첩 클래스이다.
  • java.util.HashMap$KeyIterator : HashSet 자료 구조는 사실은 내부에서 HashMap 자료 구조를 사용한다. 
  • HashMap 자료 구조에서 Value 를 사용하지 않으면 HashSet 과 같다.

 

참고: Iterator (반복자) 디자인 패턴은 객체 지향 프로그래밍에서 컬렉션의 요소들을 순회할 때 사용되는 디자인
패턴이다. 이 패턴은 컬렉션의 내부 표현 방식을 노출시키지 않으면서도 그 안의 각 요소에 순차적으로 접근할 수
있게 해준다. Iterator 패턴은 컬렉션의 구현과는 독립적으로 요소들을 탐색할 수 있는 방법을 제공하며, 이로 인해 
코드의 복잡성을 줄이고 재사용성을 높일 수 있다.