TIL - Iterator (09.24)
2025. 9. 24. 16:08

📚 오늘 공부한 내용 - Iteration (순회)

🎯 순회(Iteration)란?

순회는 자료구조에 들어있는 데이터를 차례대로 접근해서 처리하는 것을 말합니다. 여러 곳을 돌아다닌다는 의미로, 컬렉션의 모든 요소를 하나씩 방문하는 과정입니다.

🤔 문제 상황: 각기 다른 순회 방법들

각 자료구조마다 내부 구조가 다르기 때문에 순회 방법도 모두 달랐습니다:

// 배열 리스트 - 인덱스 기반
for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}

// 연결 리스트 - 노드 기반  
Node current = first;
while (current != null) {
    System.out.println(current.item);
    current = current.next;
}

문제점: 자료구조가 바뀔 때마다 순회 코드를 다시 작성해야 하고, 내부 구조까지 알아야 함


💡 해결책: Iterable & Iterator 패턴

Java는 이 문제를 해결하기 위해 Iterator 패턴을 도입했습니다. 모든 자료구조를 동일한 방법으로 순회할 수 있게 하는 일관된 인터페이스를 제공합니다.

🔧 핵심 인터페이스들

1️⃣ Iterable 인터페이스

public interface Iterable<T> {
    Iterator<T> iterator(); // 반복자를 반환하는 메서드
}
  • 의미: "반복 가능한" 객체임을 나타냄
  • 역할: Iterator를 제공하는 팩토리 메서드

2️⃣ Iterator 인터페이스

public interface Iterator<E> {
    boolean hasNext(); // 다음 요소가 있는지 확인
    E next();          // 다음 요소 반환하고 위치 이동
}
  • 의미: "반복자" - 실제 순회를 담당
  • 역할: 순회 상태를 관리하고 요소에 접근

🏗️ 직접 구현해보기

Iterator 구현체

public class MyArrayIterator implements Iterator<Integer> {
    private int currentIndex = -1;  // 현재 위치 (-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]; // 인덱스 증가하고 반환
    }
}

Iterable 구현체

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); // Iterator 생성해서 반환
    }
}

🎮 사용 방법

MyArray myArray = new MyArray(new int[]{1, 2, 3, 4});
Iterator<Integer> iterator = myArray.iterator();

// 전통적인 방법
while (iterator.hasNext()) {
    Integer value = iterator.next();
    System.out.println("value = " + value);
}

✨ 향상된 for문(Enhanced For Loop)

Iterable을 구현한 객체는 자동으로 **향상된 for문(for-each문)**을 사용할 수 있습니다!

// 깔끔한 for-each 문법
for (int value : myArray) {
    System.out.println("value = " + value);
}

🔄 컴파일러의 마법

위의 for-each문은 컴파일 시점에 다음과 같이 자동 변환됩니다:

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

두 코드는 완전히 동일합니다! for-each문이 더 간결하고 읽기 쉽죠.


🏛️ Java 컬렉션 프레임워크와 Iterator

📊 모든 컬렉션이 Iterable 구현

Java의 모든 컬렉션은 이미 Iterable과 Iterator를 구현해두었습니다:

                Iterable
                    ↓
               Collection ──────── Map
                    ↓              ↓
        ┌───────────┼───────┐      └── keySet(), values(), 
      List        Set     Queue       entrySet()으로 순회 가능
        ↓          ↓        ↓
   ArrayList   HashSet  ArrayDeque
   LinkedList  TreeSet  LinkedList

🎯 다형성의 힘

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

private static void foreach(Iterable<Integer> iterable) {
    System.out.println("iterable = " + iterable.getClass());
    for (Integer i : iterable) {
        System.out.println(i);
    }
}

// 사용법 - 어떤 컬렉션이든 동일하게 처리!
List<Integer> list = new ArrayList<>();
Set<Integer> set = new HashSet<>();

printAll(list.iterator());  // ArrayList의 Iterator
printAll(set.iterator());   // HashSet의 Iterator
foreach(list);              // ArrayList를 for-each로
foreach(set);               // HashSet을 for-each로

🎁 Iterator 패턴의 장점

1️⃣ 일관성 (Consistency)

  • 모든 자료구조를 동일한 방법으로 순회
  • 새로운 자료구조를 배워도 순회 방법은 동일

2️⃣ 추상화 (Abstraction)

  • 자료구조의 내부 구조를 몰라도 순회 가능
  • ArrayList인지 LinkedList인지 신경 쓸 필요 없음

3️⃣ 캡슐화 (Encapsulation)

  • 자료구조의 내부 구현이 외부에 노출되지 않음
  • 구현 변경 시에도 클라이언트 코드는 영향 없음

4️⃣ 다형성 (Polymorphism)

  • Iterator/Iterable 인터페이스로 통일된 처리
  • 런타임에 실제 구현체가 결정됨

5️⃣ 확장성 (Extensibility)

  • 새로운 자료구조 추가 시 기존 코드 재사용 가능
  • Iterator만 구현하면 모든 순회 알고리즘 호환

🎭 디자인 패턴의 관점

Iterator 패턴은 GoF 디자인 패턴 중 하나입니다:

  • 목적: 컬렉션의 내부 표현을 노출하지 않고 순차적으로 접근
  • 구조: Aggregate(컬렉션)와 Iterator(반복자)의 분리
  • 효과: 순회 알고리즘과 자료구조의 독립성 확보

🤝 협력 구조

Client → Iterable.iterator() → Iterator 
                                  ↓
                            hasNext() & next()
                                  ↓
                              실제 데이터

💪 실무에서의 활용

🔍 언제 사용할까?

  • 모든 요소 순회가 필요할 때
  • 자료구조에 독립적인 알고리즘 작성
  • 안전한 순회가 필요할 때 (ConcurrentModificationException 방지)

⚖️ Iterator vs 인덱스 접근

// ✅ Iterator 사용 (권장)
for (String item : list) {
    System.out.println(item);
}

// ❌ 인덱스 사용 (비권장)
for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i)); // LinkedList에서 O(n) 성능!
}

🎯 오늘의 핵심 포인트

  1. 통일된 순회: Iterator 패턴으로 모든 자료구조를 동일하게 순회
  2. 캡슐화의 힘: 내부 구조를 몰라도 순회 가능
  3. for-each의 비밀: 컴파일러가 Iterator로 자동 변환
  4. 다형성 활용: 인터페이스 기반으로 유연한 코드 작성
  5. 성능 고려: LinkedList에서 인덱스 접근 대신 Iterator 사용

 

'🧑‍💻Sparta' 카테고리의 다른 글

TIL - Collection (09.26)  (0) 2025.09.26
TIL - Comparator (09.25)  (0) 2025.09.25
TIL - Deque (09.23)  (0) 2025.09.23
TIL - Queue (09.22)  (0) 2025.09.23
TIL - Stack (09.21)  (0) 2025.09.23