티스토리 뷰

프로그램에서 데이터에 대한 반복 계산을 작성하는 방법은 여러 가지가 있습니다. for 루프와 while 루프로 배열이나 입력 등 반복되는 데이터가 있을 때 그것을 하나씩 처리합니다.

for (i=0; i<n; i++) {
    a = array[i];
    compute(a);
    print(a);
}

이 루프를 살펴보면 일단 n이라는 숫자를 미리 알아야 하고, i를 증가시키는 부분과 하나씩 [i]번째 값을 가져오는 부분이 따로 있습니다. 즉 루프를 돌리기 위한 세부적인 로직에 신경쓰는 것이 번거롭다는 것입니다. 이렇게 루프에서 값을 차례로 처리하는 간결한 모델이 자바의 for-colon 방식이어서, 루프 코드의 논리적인 번거로움을 덜어줍니다.

for (int a : array) {
    compute(a);
    print(a);
}

이 코드는 한결 간단하고 보기좋습니다. 배열에 있는 요소를 차례로 계산하고 출력한다는 것을 한눈에 알아볼 수 있습니다. 배열이외에도 콜렉션 인터페이스를 상속하는 모든 콜렉션 자료구조에 대해서도 이러한 루프를 적용할 수 있습니다.

이 for-each 루프는 뒤에 이터레이터(반복자)라고 하는 기능이 숨겨져 있습니다. 즉 위의 코드는 실제로는 다음과 같이 이터레이터를 이용한 코드로 바뀌어 컴파일됩니다. (for :은 복잡한 코드 부분으로 바뀌는 구문 장식이라고 할 수 있습니다)

Iterator<Integer> it = array.iterator();
for (int a = it.next(); it.hasNext(); a = it.next()) {
    compute(a);
    print(a);
}

이터레이터란 첫 요소부터 차례로 다음 요소를 가져오는 기능을 가지는 표준 객체를 말합니다. 여러 개의 값을 가진 자료구조에서 한 개씩 요소를 가져오는 일을 해주는 표준이라고 할 수 있습니다, 파일에서 토큰을 한 개씩 읽어오는 다음 루프에서도 반복자를 사용하고 있습니다.

while (scan.hasNext()) {
    tok = scan.next();
    process(tok);
    print(tok);
}

이 코드도 루프를 언제까지 반복해야 할지, 그리고 다음 값을 가져오는 코드를 포함합니다. 사실 여기서 사용된 hasNext()와 next()는 이터레이터 인터페이스가 가진 표준 메소드들입니다. 

이터레이터는 반복부를 처리하는 프로그래밍 기법으로 패턴이라고도 할 수 있으며, 자바 뿐 아니라 다른 언어에서도 적용되는 방법입니다. 자바에서는 이러한 패턴을 구현하기 위해 제너릭을 이용합니다.

이터레이터 패턴은 데이터를 반복적으로 처리하기 위한 기능을 제공하는데, 반복적인 데이터의 방문 또는 생성할 수 있는 기능을 Iterable이라고 하고 이터러블의 데이터를 실제로 반복할 수 있게 해 주는 부분이 이터레이터입니다. 그래서 이터레이터 패턴을 자바 언어에서 구현하기 위해 Iterable과 Iterator라는 두 개의 인터페이스가 정의되어 있습니다. 이들 인터페이스는 자바의 util 라이브러리에 다음과 같이 정의되어 있습니다.

interfaec Iterable<T> {
    Iterator<T> iterator();
}
public interface Iterator<T> {
    booleann hasNext();
    T next();
}

이터레이터 클래스를 이용하여 여러 명의 학생 중에서 차례로 한 명씩 랜덤하게 뽑는 기능을 구현해 볼 수 있습니다. 학생 여러 명의 정보를 저장한 ArrayList를 가지고  있는데, 이 중에서 랜덤하게 선발하는 기능을 가지는 클래스를 Iterator로 만들 수 있습니다. 이 Iterator는 next() 메소드로 한 번에 한 명만 랜덤하게 돌려주어야 하고 한번 선발된 학생은 다시 선발되지 않도록 아직 선발되지 않은 학생의 정보를 기억해야 합니다. 또한 선발할 학생이 더 있는지 여부를 hasNext()로 알려주어야 합니다. 

다음은 RandSelector라는 이터레이터 클래스의 사용부를 보여줍니다.

RandSelector sel = new RandSelector(mgr.mList);
for (Student st: sel) 
    st.print();
System.out.println();
for (Student st: sel) 
    st.print();
System.out.println();
for (Student st: sel)
    st.print();
System.out.println();

이 코드를 실행하면 다음과 같이 랜덤한 순서의 학생 이름이 세번 출력됩니다.

김준철 김재훈 김성관 김동현 김원철 박우현 박태주
김재훈 박태주 김원철 김동현 박우현 김준철 김성관
박우현 김준철 김동현 김재훈 김성관 박태주 김원철

그럼 이런 RandSelector 클래스의 구현을 살펴보겠습니다. 이 클래스가 Iterable을 구현하기 때문에 위 코드처럼 for-each 문으로 사용이 가능합니다.

public class RandSelector implements Iterable<Student> {
    Collection<Student> myList = null;
    // 생성자에서 리스트를 받아 기억시킨다
    RandSelector(Collection<Student> mList) {
        myList = mList;
    }
    // Iterable을 구현하기 위해 iterator 메소드로 Iterator를 리턴한다
    @Override
    public Iterator<Student> iterator() {
        return new RandIterator();
    }
    // Iterator를 구현한 내부 클래스
    private class RandIterator implements Iterator<Student> {
        List<Student> shuffleList;
        // 새로 호출될 때마다 외부 클래스의 myList를 셔플하여 기억한다.
        RandIterator() {
            shuffleList = new ArrayList<>(myList);  // 새로운 리스트 복사 생성
            Collections.shuffle(shuffleList);  // 랜덤한 순서로 섞는다
        }
        public boolean hasNext() {
            return shuffleList.size() > 0;
        }
        public Student next() {
            Student st = shuffleList.get(0);
            shuffleList.remove(st);  // 다음 요소를 꺼내 돌려주고 삭제한다
            return st;
        }
   }
}

프로젝트에서 여러 가지 콜렉션에 대해 for-each 문으로 차례로 돌려주며 뭔가 계산하는 기능을 사용하는데 이터레이터 패턴이 매우 유용합니다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
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 26 27
28 29 30
글 보관함