JAVA

Iterator, ListIterator, Enumeration

mukom 2022. 9. 29. 17:34

Iterator, ListIterator, Enumeration 은 컬렉션의 저장 요소에 접근하는 데에 사용되는 인터페이스이다.

Enumeration 은 legacy 클래스로 사용이 자제되고 있으며, Iterator 또는 더 향상된 ListIterator 를 사용해야 한다.

 

1.  Iterator

 

Iterator 는 컬렉션에 저장된 데이터를 제어하고자 하는 목적이 아닌, 순차 탐색을 목적하고 있다.

 

Collection 인터페이스에는 Iterator 를 반환하는 iterator() 메서드를 정의하고 있다.

즉, List와 Set 또한 이를 포함하고 있고 이들을 구현한 클래스에   iterator() 메서드를 호출하여 Iterator 를 반환받아 

반복문(for, while) 등을 이용하여 컬렉션의 요소를 읽을 수 있게 된다.

class IteratorTest {
	
    Collection collection = new ArrayList();
    Iterator iterator = collection.iterator();
    
    while(iterator.hasNext()) {                  
    	System.out.println(iterator.next());
    }
}

위의 예시 코드에 대해 설명하자면 다음과 같다. 

 

먼저 다형성을 이용한 부모 인터페이스 Collection의 참조 변수에 인터페이스 List를 구현한 ArrayList 인스턴스 주소값을 참조하게 한다.

🖍 ArrayList 자리에는 Collection 인터페이스를 구현한 모든 클래스가 올 수 있기 때문에 코드를 많이 수정할 필요가 없다.

 

해당 참조 변수에 저장된 요소를 읽어 오기 위해 인터페이스 Iterator 타입의 참조 변수에

Collection 타입의 참조 변수를 통한 iterator() 메서드를 호출한 리턴 값을 저장한다.

 

반복문 while 을 통해 참조 변수 iterator 에 저장된 요소를 읽어 오는 코드를 작성하였다.

hasNext() 메서드는 커서를 바탕으로 다음 읽어 올 요소가 있는지 확인하고 있다면 true, 없다면 false 를 반환한다.

next() 메서드는 커서 이동을 하여 다음 요소를 반환하는데, 만일 반환할 요소가 없으면 NoSuchElementException을 발생시키기 때문에 hasNext() 메서드를 통해서 next() 를 호출하는 편이 안전하다.

 

 

 Map 인터페이스 타입 그대로는 Collection 인터페이스에 정의된 iterator() 를 사용할 수 없다.

하지만 Map 의 entrySet() 메서드나 keySet() 메서드 등을 활용하여 Set 타입으로 변형하여  iterator() 를 사용할 수 있다.

class IteratorTest {
	
    Map<String, Integer> map = new HashMap<String, Integer>();
		
		map.put("가", 1);
		map.put("나", 2);
		map.put("다", 3);
    
    // entrySet    
	Set<Entry<String, Integer>> set = map.entrySet();
    Iterator<Entry<String, Integer>> iterator = set.iterator();
    
    while (iterator.hasNext()) {
		Entry<String, Integer> e1 = iterator.next();
		System.out.println(e1.getKey() + ":" + e1.getValue());     // 가:1, 다:3,나:2
	}
    
    // keySet
    Set<String> set2 = map.keySet();
    Iterator<String> iterator2 = set2.iterator();
    
    while (iterator2.hasNext()) {
		String key = iterator2.next(); 
		System.out.println(key + ":" + map.get(key));
	}
}

entrySet() 메서드를 활용하면 Entry 자체를 하나로 만들어 Set 으로 만들 수 있게 되어 그 자체로 키는 물론 값까지 확인할 수 있다.

 

keySet() 메서드는 키만 Set 으로 만들었기 때문에 값을 확인하기 위해서는 원본 map 에서 get() 메서드를 호출하여 값을 확인해야 한다. 

🖍 중복되는 데이터를 가지고 있을 수 있는 값으로는 Set 을 만들 수 없다.

 

 

2. Java update 적용

 

🔹 Enhanced for 문

// Enhanced for 문 사용
for(Entry<String, Integer> e1 : map.entrySet()) {
	System.out.println(e1.getKey() + ":" + e1.getValue());
}
		
for (String key : map.keySet()) {
	System.out.println(key + ":" + map.get(key));
}

향상된 for 문의 등장으로 Iterator 를 굳이 사용하지 않아도 저장된 요소를 확인할 수 있게 되었다.

하지만 아직까지도 두 가지의 Set 형태를 두 번 반복하듯 확인해야 한다.

이를 한 번에 확인할 수 있는 것이 바로 아래의 foreach 이다.

 

🔹foreach

// foreach
map.forEach(new BiConsumer<String, Integer>() {
	@Override
	public void accept(String t, Integer u) {
		System.out.println(t + ":" + u);
	}
});

foreach 를 이용하게 되면서 Set 타입으로 굳이 변경하지 않아도 Map 에서 바로 요소 확인이 가능해졌다.

이를 더 효과적으로 줄이는 방법이 그 다음 Lamda 식이다.

 

🔹Lamda

// Lamda
map.forEach((t, u) -> System.out.println(t + ":" + u));

 

Lamda 식을 통해서 앞서 했던 모든 코드가 한 줄로 작성이 가능해졌다.

극단적으로 줄어든 만큼 단점도 존재하기는 하다.

코드가 한 줄로 처리되기 때문에 어느 부분에서 문제가 생기는지 디버깅에 어려움이 생긴다.

 

 

 

 3. ListIterator와 Enumeration

 

Enumeration 은 앞서 말했듯 Iterator 의 구버전으로 하위 호환을 위해 남겨 놓았을 뿐 사용을 자제해야 한다.

 

ListIterator 는 Iterator 를 상속받아 기능을 더 추가한 것으로 단방향 접근을 하는 Iterator 와 달리 양방향 접근이 가능하다.

다만 이름에서도 알 수 있듯이 List 를 구현한 컬렉션만 사용이 가능하다.

양방향이라는 것은 이전 요소에도 접근이 가능하다는 것으로 즉  index 를 사용할 수 있다는 것이다.