컬렉션 프레임워크의 핵심: List 인터페이스 완벽 분석

제목: 컬렉션 순회의 기본: `Iterator`

1. Iterator 인터페이스란 무엇일까요?

Iterator 인터페이스는 자바 컬렉션 프레임워크에서 컬렉션의 요소들을 순차적으로 접근하고 조작하는 방법을 표준화한 인터페이스입니다. Collection 인터페이스를 구현하는 모든 클래스 (예: List, Set 등)는 iterator() 메서드를 통해 Iterator 객체를 반환할 수 있습니다. Iterator를 사용하면 컬렉션의 내부 구조에 상관없이 일관된 방법으로 요소에 접근하고, 필요한 경우 안전하게 요소를 제거할 수도 있습니다.
1.1. Iterator 인터페이스의 주요 특징

  • 표준화된 순회 방법: Iterator는 컬렉션의 종류(예: ArrayList, LinkedList, HashSet, TreeSet)에 관계없이 일관된 방식으로 요소에 접근하는 방법을 제공합니다. 따라서, 개발자는 컬렉션의 내부 구조에 구애받지 않고 코드를 작성할 수 있으며, 코드 재사용성을 높일 수 있습니다.

  • 단방향 순회: Iterator는 단방향으로만 순회할 수 있습니다. 즉, 다음 요소로 이동(next())하는 것은 가능하지만, 이전 요소로 돌아가거나 특정 인덱스로 바로 이동하는 기능은 제공하지 않습니다. 만약 양방향으로 순회하거나 특정 위치로 이동해야 할 경우에는 ListIterator를 사용해야 합니다.
  • 안전한 요소 제거: Iteratorremove() 메서드를 통해 컬렉션의 요소를 안전하게 제거할 수 있는 기능을 제공합니다. 일반적인 for 루프 또는 향상된 for 루프(for-each 루프)를 사용하여 컬렉션을 순회하는 동안 요소를 제거하려고 하면 예외가 발생할 수 있지만, Iteratorremove() 메서드를 사용하면 이러한 예외를 방지할 수 있습니다.

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

  • boolean hasNext(): 컬렉션에서 다음 요소가 존재하는지 여부를 확인합니다. 다음 요소가 있다면 true를 반환하고, 더 이상 요소가 없다면 false를 반환합니다. hasNext() 메서드는 주로 루프의 조건으로 사용되며, 컬렉션에 순회할 요소가 남아 있는지 확인하는 데 필수적입니다.

  • E next(): 컬렉션에서 다음 요소를 반환합니다. next() 메서드를 호출하기 전에 반드시 hasNext() 메서드를 호출하여 다음 요소가 존재하는지 확인해야 합니다. 만약 다음 요소가 없는데 next() 메서드를 호출하면 NoSuchElementException 예외가 발생합니다. 반환 타입 E는 제네릭 타입으로 컬렉션에 저장된 요소의 타입을 나타냅니다.
  • void remove(): next() 메서드를 통해 반환된 가장 최근 요소를 컬렉션에서 제거합니다. remove() 메서드는 next() 메서드를 호출한 후 단 한 번만 호출할 수 있습니다. 만약 next() 메서드를 호출하지 않고 remove() 메서드를 호출하거나, next() 메서드를 여러 번 호출하고 remove() 메서드를 호출하면 IllegalStateException이 발생합니다.

3. Iterator 사용 방법 및 예시

Iterator를 사용하여 컬렉션 요소를 순회하는 기본 패턴은 다음과 같습니다.

3.1. iterator() 메서드 호출: 컬렉션 객체의 iterator() 메서드를 호출하여 Iterator 객체를 얻습니다.

1
2
3
4
5
6
List<String> list = new ArrayList<>();
list.add("사과");
list.add("바나나");
list.add("체리");

Iterator<String> iterator = list.iterator();

3.2. while 루프를 이용한 순회: hasNext() 메서드를 사용하여 다음 요소가 있는지 확인하고, next() 메서드를 사용하여 다음 요소를 가져옵니다.

1
2
3
4
while (iterator.hasNext()) {
    String element = iterator.next();
    System.out.println(element);
}

3.3. remove() 메서드 사용: remove() 메서드를 사용하여 컬렉션에서 특정 요소를 안전하게 제거할 수 있습니다. 주의할 점은 remove() 메서드를 호출하기 전에 반드시 next() 메서드를 호출해야 한다는 것입니다.

1
2
3
4
5
6
7
8
9
10
11
List<String> list = new ArrayList<>(Arrays.asList("사과", "바나나", "체리", "포도"));
Iterator<String> iterator = list.iterator();

while (iterator.hasNext()) {
    String element = iterator.next();
    if (element.equals("바나나")) {
        iterator.remove(); // 안전하게 요소 제거
    }
}

System.out.println(list); // 출력: [사과, 체리, 포도]

위 예시에서는 문자열 바나나를 만나면 해당 요소를 리스트에서 제거합니다. remove() 메서드는 next() 메서드를 통해 가져온 요소만을 삭제할 수 있다는 점을 기억해야 합니다.

4. Iterator의 특징 및 장점 상세 분석

4.1. 표준화된 순회 방법: Iterator는 컬렉션의 구체적인 구현 방식(배열 기반인지, 링크드 리스트 기반인지 등)에 관계없이, 즉 ArrayList, LinkedList, HashSet, TreeSet 과 같은 다양한 종류의 컬렉션에서 일관된 방법으로 요소에 접근할 수 있도록 합니다. 즉, 코드 재사용성이 높으며, 컬렉션의 내부 구현이 변경되어도 Iterator를 사용하는 코드는 변경할 필요가 없습니다.

4.2. 안전한 요소 제거: 일반적인 for 루프나 향상된 for 루프(for-each 루프)를 사용하여 컬렉션을 순회하는 동안 요소를 제거하려고 하면, ConcurrentModificationException과 같은 예외가 발생할 수 있습니다. ConcurrentModificationException은 컬렉션을 순회하는 도중에 구조가 변경될 때 발생하는데, 예를 들어, for-each 루프나 인덱스 기반의 for 루프에서 요소를 제거하는 도중에 요소 이동이 발생하면서 예기치 않은 예외가 발생할 수 있습니다. 반면, Iteratorremove() 메서드를 사용하면 컬렉션의 구조 변경을 안전하게 처리하면서 요소를 제거할 수 있습니다. 따라서, 스레드 환경에서 컬렉션을 사용할 때, Iterator를 통한 요소 제거가 안전성을 확보하는 데 더 유리할 수 있습니다.

4.3 코드 재사용성: Iterator는 제네릭 타입으로 정의되어 있기 때문에 다양한 타입의 컬렉션에서 동일한 코드를 사용하여 요소를 순회할 수 있습니다. 즉, 코드의 타입 안정성을 유지하면서도 재사용성을 높일 수 있습니다.

1
2
3
4
5
List<String> stringList = new ArrayList<>(Arrays.asList("A", "B", "C"));
Iterator<String> stringIterator = stringList.iterator();

List<Integer> intList = new ArrayList<>(Arrays.asList(1, 2, 3));
Iterator<Integer> intIterator = intList.iterator();

위 코드처럼 Iterator<String>Iterator<Integer>와 같이 다른 타입의 컬렉션을 순회하는 코드를 하나의 메서드로 정의하고 재사용하는 것도 가능합니다.

4.4. 내부 익명 클래스 iterator() 메서드를 호출하면, Collection 인터페이스를 구현하는 컬렉션 클래스 내부에 정의된 익명 클래스Iterator 객체를 생성하여 반환합니다. 이 익명 클래스는 특정 컬렉션의 내부 구조에 특화되어 있으며, 해당 컬렉션의 요소들을 효율적으로 순회할 수 있도록 구현되어 있습니다. 따라서, 개발자는 내부 구현을 직접 관리하지 않아도 되며, Iterator 인터페이스를 통해 편리하게 컬렉션을 사용할 수 있습니다.

5. Iterable 인터페이스와 향상된 for 루프 (for-each loop) Collection 인터페이스는 Iterable 인터페이스를 확장하고 있습니다. Iterable 인터페이스에는 iterator() 메서드가 정의되어 있으며, 이 메서드를 구현한 클래스는 향상된 for 루프(for-each loop)를 사용하여 요소를 순회할 수 있습니다. 따라서 Collection 인터페이스를 구현한 클래스들은 자연스럽게 향상된 for 루프를 사용할 수 있는 것입니다.

향상된 for 루프는 Iterator를 직접 사용하는 것에 비해 코드를 더 간결하게 만들어 주지만, 내부적으로는 Iterator를 사용하여 컬렉션을 순회합니다. 하지만 향상된 for 루프에서는 remove() 메서드를 직접적으로 사용할 수 없으므로, 컬렉션 요소를 제거하면서 순회해야 하는 경우에는 Iterator를 직접 사용해야 합니다.

1
2
3
4
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
for (String element : list) {
    System.out.println(element);
}

위 코드에서 향상된 for 루프는 Iterator를 사용한 것과 동일하게 리스트의 모든 요소를 출력합니다.

6. Iterator 사용 시 추가적인 주의 사항

  • 단방향 순회: Iterator는 한 방향으로만 순회하므로, 한번 next() 메서드를 호출하여 다음 요소로 이동하면, 다시 이전 요소로 돌아갈 수 없습니다. 만약 양방향 순회가 필요하다면 ListIterator를 사용해야 합니다.

  • 요소 변경: Iterator를 사용하여 컬렉션을 순회하는 도중에 컬렉션 자체에 요소를 추가하거나 삭제하는 등의 구조적 변경을 하면 ConcurrentModificationException이 발생할 수 있습니다. 요소를 안전하게 제거하려면 Iteratorremove() 메서드를 사용하거나, 요소 변경이 필요하면 ListIterator를 사용해야 합니다.
  • 병렬 처리: 멀티스레드 환경에서 여러 스레드가 동시에 하나의 Iterator를 사용하여 컬렉션을 변경하려고 시도하면 예기치 않은 결과가 발생하거나 예외가 발생할 수 있습니다. 이러한 경우에는 동기화 메커니즘을 사용하거나, 동시성 컬렉션 클래스(ConcurrentHashMap 등)를 사용하는 것이 더 안전합니다.

Categories:

Updated:

Leave a comment