컬렉션 프레임워크의 핵심: List 인터페이스 완벽 분석
제목: 컬렉션 순회의 기본: `Iterator`
1. Iterator
인터페이스란 무엇일까요?
Iterator
인터페이스는 자바 컬렉션 프레임워크에서 컬렉션의 요소들을 순차적으로 접근하고 조작하는 방법을 표준화한 인터페이스입니다. Collection
인터페이스를 구현하는 모든 클래스 (예: List
, Set
등)는 iterator()
메서드를 통해 Iterator
객체를 반환할 수 있습니다. Iterator
를 사용하면 컬렉션의 내부 구조에 상관없이 일관된 방법으로 요소에 접근하고, 필요한 경우 안전하게 요소를 제거할 수도 있습니다.
1.1. Iterator
인터페이스의 주요 특징
-
표준화된 순회 방법:
Iterator
는 컬렉션의 종류(예:ArrayList
,LinkedList
,HashSet
,TreeSet
)에 관계없이 일관된 방식으로 요소에 접근하는 방법을 제공합니다. 따라서, 개발자는 컬렉션의 내부 구조에 구애받지 않고 코드를 작성할 수 있으며, 코드 재사용성을 높일 수 있습니다. - 단방향 순회:
Iterator
는 단방향으로만 순회할 수 있습니다. 즉, 다음 요소로 이동(next()
)하는 것은 가능하지만, 이전 요소로 돌아가거나 특정 인덱스로 바로 이동하는 기능은 제공하지 않습니다. 만약 양방향으로 순회하거나 특정 위치로 이동해야 할 경우에는ListIterator
를 사용해야 합니다. - 안전한 요소 제거:
Iterator
는remove()
메서드를 통해 컬렉션의 요소를 안전하게 제거할 수 있는 기능을 제공합니다. 일반적인for
루프 또는 향상된for
루프(for-each
루프)를 사용하여 컬렉션을 순회하는 동안 요소를 제거하려고 하면 예외가 발생할 수 있지만,Iterator
의remove()
메서드를 사용하면 이러한 예외를 방지할 수 있습니다.
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
루프에서 요소를 제거하는 도중에 요소 이동이 발생하면서 예기치 않은 예외가 발생할 수 있습니다. 반면, Iterator
의 remove()
메서드를 사용하면 컬렉션의 구조 변경을 안전하게 처리하면서 요소를 제거할 수 있습니다. 따라서, 스레드 환경에서 컬렉션을 사용할 때, 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
이 발생할 수 있습니다. 요소를 안전하게 제거하려면Iterator
의remove()
메서드를 사용하거나, 요소 변경이 필요하면ListIterator
를 사용해야 합니다. - 병렬 처리: 멀티스레드 환경에서 여러 스레드가 동시에 하나의
Iterator
를 사용하여 컬렉션을 변경하려고 시도하면 예기치 않은 결과가 발생하거나 예외가 발생할 수 있습니다. 이러한 경우에는 동기화 메커니즘을 사용하거나, 동시성 컬렉션 클래스(ConcurrentHashMap
등)를 사용하는 것이 더 안전합니다.
Leave a comment