Java :: Collection :: 1.5 Iterator

김병철·2022년 9월 4일
0

Java

목록 보기
5/20

Java의 정석 3판을 보며 공부한 내용을 정리하였습니다.
남궁성님께 많이 배우고 있습니다.

1.5 Iterator, ListIterator, Enumeration

이 셋은 모두 컬렉션에 저장된 요소를 접근하는데 사용되는 인터페이스이다.

Iterator

  • 컬렉션에 저장된 요소들을 읽어오는 방법을 표준화

  • 저장된 각 요소에 접근하는 기능을 가진 Iterator인터페이스를 정의

  • Collection 인터페이스에는 Iterator를 반환하는 iterator()를 정의
    (List, Set은 Collection를 상속하므로 iterator()가 있음.)

  • iterator()를 호출해서 Iterator를 얻어서 반복문을 사용해서 각 요소에 접근

public interface Iterator {
	boolean hasNext();
    object next();
    void remove();
}

public interface Collection {
	...
    public Iterator iterator();
    ...
}

Iterator 인터페이스의 메서드

  • boolean hasNext()
    -> 읽어 올 요소가 남아있는지 확인. 있으면 true, 없으면 false
  • Object next()
    -> 다음 요소 읽기. next()를 호출하기 전에 hasNext()를 호출해서 꼭 확인할 것
  • void remove()
    -> next()로 읽은 요소 삭제. next()를 호출한 뒤에 remove()를 호출해야 한다.(선택적 기능)

ArrayList에 저장된 요소 출력하기 위한 코드

Collection c = new ArrayList();	// 다른 컬렉션으로 변경시 이 부분만 고치면 된다.
Iterator it = c.iterator();

while(it.hasNext()) {
	System.out.println(it.next());
}

ArrayList 대신 Collection 인터페이스를 구현한 다른 컬렉션 클래스에서도 동일하게 사용 가능하다.

표준화했기 때문에 이렇게 코드의 재사용성이 높다.

  • 위 코드에서 Collection c를 사용 한 이유 :
    Collection에 없고 ArrayList에만 있는 메서드를 사용하는 것이 아니라면, Collection타입의 참조변수를 사용하는 것이 좋다.
    예를 들어, LinkedList로 바꿔야 할 경우에 선언문 하나만 변경하면 되기 때문이다.
Collection c = ArrayList();	// ArrayList 사용 시

Collection c = LinkedList();	// LinkedList 사용 시

아래는 관련 예제이다.

...
public static void main(String[] args){
	ArrayList list = new ArrayList();
    list.add("1");
    list.add("2");
    list.add("3");
    list.add("4");
    list.add("5");
    
    Iterator it = list.iterator();
    
    while(it.hasNext()) {
    	Object obj = it.next();
        System.out.println(obj);
	}
}
...

List는 저장순서를 유지하므로 Iterator를 이용해서 읽은 결과 역시 저장순서와 동일하지만

Set클래스들은 각 요소간 순서가 유지되지 않아서 Iterator로 읽은 요소는 저장순서와 같지 않다.


Map 인터페이스

Map 인터페이스를 구현한 컬렉션 클래스는 키(key)와 값(value) 을 쌍(pair) 으로 저장하기 때문에, iterator()를 직접 호출 불가!

대신 keySet()이나 entrySet()과 같은 메서드를 통해 키와 값을 각각 Set 형태로 얻어서 다시 iterator()를 호출해야 Iterator를 얻을 수 있다.

Map map = new HashMap();
	...
Iterator it = map.extrySet().iterator();	// 아래의 주석 문장 두개를 하나로 합친 것

/*
Set eSet = map.extrySet();
Iterator it = eSet.iterator();
/*

1. map.entrySet()의 실행결과가 Set 이므로

Iterator it = map.extrySet().iterator();	// Iterator it = Set인스턴스.iterator();

2. map.entrySet()를 통해 얻은 Set인스턴스의 iterator()를 호출해서 Iterator인스턴스를 얻는다.

Iterator it = Set인스턴스.iterator();	// Iterator it = Iterator인스턴스;

3. 마지막으로 Iterator인스턴스의 참조가 it 에 저장된다.

StringBuffer 사용 시 아래와 같이 코드를 쓴다.

StringBuffer sb = new StringBuffer();
sb.append("A");
sb.append("B");
sb.append("C");

이 코드는 아래와 같이 간단히 쓸 수 있다.

StringBuffer sb = new StringBuffer();
sb.append("A").append("B").append("C");

append 메서드가 StringBuffer로 반환하기 때문이다.


ListIterator와 Enumeration

Enumeration은 컬렉션 프레임웍이 만들어지기 이전에 사용하던 것으로 Iterator의 구버전과 같다.

이전 버전과의 호환을 위해 남겨둔 것이므로 가능하면 Enumeration 대신 Iterator를 사용하자.

  • ListIterator는 Iterator를 상속받아 기능을 추가한 것

  • Iterator는 단방향이지만, ListIterator는 양방향 이동 가능!
    (단, ArrayList나 LinkedList와 같이 List인터페이스를 구현한 컬렉션에서만!)

  • Enumeration과 Iterator는 메서드 이름만 다를 뿐 기능은 같다.
    (ListIterator는 Iterator + 이전방향으로 접근기능)


Enumeration 인터페이스와 메서드

  • boolean hasMoreElements()
    -> 읽어올 요소가 남아있는지 확인. 있으면 true, 없으면 false 반환.
  • Object nextElement()
    -> 다음 요소 읽기. nextElement()를 호출 전 hasMoreElements()를 호출해야 한다. (Iterator의 next()와 같다.)

ListIterator의 메서드

  • void add(Object o)
    -> 컬렉션에 새로운 객체(o) 추가. (선택적 기능)
  • boolean hasNext()
    -> 읽을 다음 요소가 남아있는지 확인. 있으면 true, 없으면 false 반환
  • boolean hasPrevious()
    -> 읽을 이전 요소가 남아있는지 확인. 있으면 true, 없으면 false 반환
  • Object next()
    -> 다음 요소 읽기. next()호출 전 hasNext() 확인할 것.
  • Object previous()
    -> 이전 요소 읽기. previous()호출 전 hasNext() 확인할 것.
  • int nextIndex()
    -> 다음 요소 index 반환
  • int previousIndex()
    -> 이전 요소 index 반환
  • void remove()
    -> next() 또는 previous()로 읽은 요소 삭제. 반드시 next()나 previous()를 먼저 호출한 뒤 이 메서드를 호출해야 한다.(선택적 기능)
  • void set(Object o)
    -> next() 또는 previous()로 읽은 요소를 지정된 객체(o)로 변경. 반드시 next()나 previous()를 먼저 호출한 뒤 이 메서드를 호출해야 한다.(선택적 기능)

ListIterator의 사용 예

public static void main(String[] args){
	ArrayList list = new ArrayList();
    list.add("1");
    list.add("2");
    list.add("3");
    list.add("4");
    list.add("5");
    
    ListIterator it = list.listIterator();
    
    while(it.hasNext()) {
    	System.out.print(it.next());	// 순방향으로만 진행하면서 읽는다.
    }
    System.out.println();
    
    while(it.hasPrevious()) {
    	System.out.print(it.previous());	// 역방향으로만 진행하면서 읽는다.
    }
    System.out.println();
}
  • next()나 previous()로 이동하기 전에 꼭 hasNext() 나 hasPrevious()로 확인해야 한다.

  • 선택적 기능들은 꼭 구현하지 않아도 된다. 하지만 인터페이스로부터 상속받은 메서드는 추상메서드라 메서드의 몸통(body)를 반드시 만들어 줘야 하므로 다음과 같이 처리한다.

public void remove() {
	throw new UnsupportedOperationException();
}

단순하게 public void remove(){}; 와 같이 구현하기보다는 위와 같이 예외를 던져서 구현되지 않는 기능이라는 것을 메서드 호출한 곳에 알리는 것이 좋다.

Java API 문서에 remove()의 상세 내용을 보면 remove메서드를 지원하지 않는 Iterator는 UnsupportedOperationException을 발생시킨다고 쓰여 있다.

즉, remove메서드를 구현하지 않으면 UnsupportedOperationException을 발생시키도록 구현하라는 뜻이다.

  • Iterator의 remove()는 단독으로 쓰일 수 없고, next()와 함께 써야 한다.
    (next()없이 단독으로 remove()가 호출되면 IllegalStateException이 발생)

next()와 remove() 사용시 cursor 위치

...
public static void main(String[] args){
	MyVector2 v = new MyVector2();
    v.add("0");
    v.add("1");
    v.add("2");
    v.add("3");
    v.add("4");
    
    System.out.println("삭제 전 : " + v);
    Iterator it = v.iterator();
    it.next();
    it.remove();
    it.next();
    it.remove();
    
    System.out.println("삭제 후 : " + v);
}
...

MyVector2객체를 생성하고 데이터를 저장한 뒤 Iterator를 통해 첫 번째와 두 번째에 저장된 데이터를 삭제한다.

출력결과 :

삭제 전 : [0, 1, 2, 3, 4]
삭제 후 : [2, 3, 4]
profile
keep going on~

0개의 댓글