순회1 - 직접 구현하는 Iterable, Iterator

2024. 6. 21. 15:27김영한 Java/컬렉션 프레임워크

순회라는 단어는 여러 곳을 돌아다닌다는 뜻이다.

자료 구조에 순회는 자료 구조에 들어있는 데이터를 차례대로 접근해서 처리하는 것을 순회라 한다.

그런데 다양한 자료 구조가 있고, 각각의 자료 구조마다 데이터를 접근하는 방법이 모두 다르다.

예를 들어서 배열 리스트는 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{
	boolean hasNext();
    E next();
}
  • hasNext() : 다음 요소가 있는지 확인한다. 다음요소가 없으면 false를 반환한다.
  • next() : 다음 요소를 반환한다. 내부에 있는 위치를 다음으로 이동한다.

 

자료구조에 들어있는 데이터를 처음부터 끝까지 순회하는 방법은 단순하다. 자료 구조에 다음 요소가 있는지 물어보고, 있으면 다음 요소를 꺼내는 과정을 반복하면 된다. 만약 다음 요소가 없다면 종료하면 된다. 이렇게 하면 자료 구조에 있는 모든 데이터를 순회할 수 있다.

 

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

 

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

package collection.iterable;

import java.util.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];
    }
}
  • 생성자를 통해 반복자가 사용할 배열을 참조한다. 여기서 참조한 배열을 순회할 것이다.
  • currenIndex : 현재 인덱스, next()를 호출할 때마다 하나씩 증가한다.
  • hasNext() : 다음 항목이 있는지 검사한다. 배열의 끝에 다다르면 순회가 끝났으므로 false를 반환한다.
    • 참고로 인덱스의 길이는 0부터 시작하므로 배열의 길이에 1을 빼야 마지막 인덱스가 나온다.
  • next() : 다음 항목을 반환한다.
    • currentIndex를 하나 증가하고 항목을 반환한다.
    • 인덱스는 0부터 시작하기 때문에 currentIndex는 처음에는 -1을 가진다. 이렇게 하면 다음 항목을 조회했을 때 0이 된다. 따라서 처음 next()를 호출하면 0번 인덱스를 가리킨다.

 

Iterator는 단독으로 사용할 수 없다. Iterator를 통해 순회의 대상이 되는 자료 구조를 만들어보자.

여기서는 매우 간단한 자료 구조를 하나 만들자. 내부에는 숫자 배열을 보관한다.

 

 

package collection.iterable;

import java.util.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);
    }
}
  • 배열을 가지는 매우 단순한 자료 구조이다.
  • Iterable 인터페이스를 구현한다.
    • 이 인터페이스는 이 자료 구조에 사용할 반복자(Iterator)를 반환하면 된다.
    • 앞서 만든 반복자인 MyArrayIterator를 반환한다.
    • 이때 MyArrayIterator는 생성자를 통해 MyArray의 내부 배열인 numbers를 참조한다.
package collection.iterable;

import java.util.Iterator;

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가 가진 내부 데이터를 순회할 수 있다.

 

'김영한 Java > 컬렉션 프레임워크' 카테고리의 다른 글

순회3 - 자바가 제공하는 Iterable, Iterator  (0) 2024.06.21
순회2 - 향상된 for문  (0) 2024.06.21
Deque와 Stack, Queue  (0) 2024.06.20
Deque 자료 구조  (0) 2024.06.20
Stack - 스택 자료 구조  (0) 2024.06.17