어떤 자료구조든 일관성 있게 순회하는 방법이 있으면 개발자 입장에서 매우 편리할 것.
-> 이 문제를 해결하기 위해 Iterable 과 Iterator 인터페이스를 제공한다.
public interface Iterable<T> {
Iterator<T> iterator();
}
public interface Iterator<E> {
boolean hasNext();
E next();
}
hasNext() : 다음 요소가 있는지 확인, 다음요소가 없다면 false 반환next() : 다음 요소를 반환, 내부 위치를 다음으로 이동위 두가지는 모두 인터페이스이기 때문에 각 각 구현체는 따로 만들어서 사용해야한다.
| Iterable | 컬렉션이 반복 가능한 객체임을 나타냄 | for-each 문에서 컬렉션 순회 시 사용 |
|---|---|---|
| Iterator | 컬렉션의 요소를 하나씩 순회하는 방법 제공 | 컬렉션 순회 중 삭제나 커스텀 로직이 필요할 때 |
for-each문이라고 불리는 향상된 for문은 자료구조를 순회하는 것이 목적이다.
향상된 for문을 작성하면 Java는 컴파일 시점에 iterator를 사용한 것과 동일하게 코드를 변경한다.
-> 모든 데이터를 순회한다면 쓰기 편한 향상된 for문을 사용하는 것이 좋다.

Key 나 Value 를 정해서 순회할 수 있다.keySet() , values() 를 호출하면 Set , Collection 을 반환하기 때문에 순회가 가능하다. 물론 Entry 를 Set 구조로 반환하는 entrySet() 도 순회가 가능하다.Iterator (반복자) 디자인패턴
객체지향 프로그래밍에서 컬렉션의 요소들을 순회할 때 사용하는 디자인 패턴.
컬렉션의 내부 구조를 드러내지 않으면서 컬렉션의 각 데이터들을 순회하는 알고리즘 전략을 정의하는 것
어떤 종류의 컬렉션에서도 Iterator만 뽑아서 사용하면 여러 전략으로 순회할 수 있어 보다 더 다형적인 코드를 설계할 수 있게된다.
- JCF에서 각종 컬렉션을 쉽게 순회할 수 있는 것도 내부에 미리 반복자 패턴이 적용되어 있기 때문이다.
List<String> names = new ArrayList<>(Arrays.asList("Alice", "Bob", "Charlie"));
for (String name : names) {
if (name.equals("Bob")) {
names.remove(name);
}
}
for-each는 내부적으로 Iterator를 사용하지만, 개발자가 직접 Iterator 객체를 제어할 수 없다.
따라서 리스트를 순회하면서 요소를 삭제하면 컬렉션이 수정되었음에도 Iterator가 이를 인지하지 못해 ConcurrentModificationException이 아래와 같이 발생한다.
( 런타임 오류이기 때문에 주의할 것 )

그럼 어떻게 사용해야 하나?
List<String> names = new ArrayList<>(Arrays.asList("Alice", "Bob", "Charlie"));
Iterator<String> iterator = names.iterator();
while (iterator.hasNext()) {
if (iterator.next().equals("Bob")) {
iterator.remove();
}
}
Iterator를 사용하면 안전하게 제거할 수 있다.
Iterable을 처음부터 구현하기는 까다롭지만, 원소들의 묶음을 표현하는 타입을 작성해야 한다면 Iterable을 구현해보자. 구현해두면 그 타입을 사용하는 개발자가 for-each문을 사용할 때마다 감사해 할 것이다.
정렬을 할 때 내가 원하는대로 정렬하고 싶다면 비교자(Comparator)를 사용해서 정렬하면 된다.
public static void main(String[] args) {
Integer[] array = {3, 2, 1};
Arrays.sort(array, new AscComparator());
System.out.println(Arrays.toString(array));
}
static class AscComparator implements Comparator<Integer> {
@Override
public int compare(Integer o1, Integer o2) {
System.out.println("o1 = " + o1 + ",o2 = " + o2);
return (o1 < o2) ? -1 : (o1 == o2) ? 0 : 1;
}
Java에서 기본으로 제공하는 Integer, String 객체들을 제외한 내가 직접 만든 객체들을 정렬하려면 Comparable 을 사용한다. 내가 만든 객체이기 때문에 어떤 객체가 더 큰지는 직접 알려줘야 하기 때문이다.
public static void main(String[] args) {
MyUser a = new MyUser("a", 12);
MyUser b = new MyUser("b", 20);
MyUser c = new MyUser("c", 34);
MyUser[] userList = {a, b, c};
Arrays.sort(userList);
System.out.println(Arrays.toString(userList));
}
public class MyUser implements Comparable<MyUser> {
private String id;
private int age;
@Override
public int compareTo(MyUser o) {
return this.age < o.age ? -1 : (this.age == o.age) ? 0 : 1;
}
}
위와 같은 형태로 객체에 Comparable 을 구현하도록 하여 compareTo 를 재정의해주면 된다.
-> 객체간의 기본 정렬을 구현해주는 것
-> Arrays.sort(userList) 를 해주면 내가 구현했던 정렬 방식으로 정렬된다.
Comparable 인터페이스를 사용하여 비교Comparator를 만들어서 넘겨준다.Comparable 은 무시하고 별도로 전달해준 비교자를 사용해서 정렬한다.| Comparable | 객체 내부에 한 가지 정렬 기준을 정의 | 기본 정렬 기준이 명확할 때 |
|---|---|---|
| Comparator | 객체 외부에서 다양한 정렬 기준을 정의 | 여러 기준으로 정렬해야 할 때 |
주의할 사항
Comparable도 구현하지 않고,Comparator도 제공하지 않으면 다음과 같은 런타임 오류가 발생
Comparable로 비교해서 정렬 (자연적인 순서로 비교) == 기본 정렬Comparator를 넘겨주면 된다.Collections.sort(list) 보단 list.sort()를 권장한다.
- Collections.sort(list) 내부적으로 Arrays.sort()를 사용하는데 리스트를 배열로 변환한 뒤 정렬하고 다시 리스트로 변환해야 하기 때문에 추가적인 비용이 든다.
- list.sort()는 정렬 메서드가 객체 내부에 존재하기 때문에 외부 클래스인 Collections를 사용하는 것보다 객체 지향적이다.
TreeSet 과 같은 이진 탐색 트리 구조는 데이터를 보관할 때, 데이터를 정렬하면서 보관
-> 정렬 기준이 없으면 데이터 구조 자체가 Tree가 아니게 됨
이진 탐색 트리는 데이터를 저장할 때, 어느쪽 노드에 저장을 해야할 지 비교가 필수이기 때문에 TreeSet과 TreeMap은 Comparable 또는 Comparator가 꼭 필요하다.
// 별도의 비교자가 없으면 객체가 구현한 `Comparable`을 사용한다.
new TreeSet<>();
// Comparator를 사용하고 싶다면 인수로 넣어주면 된다.
new TreeSet<>(new IdComparator());
주의할 사항
Comparable도 구현하지 않고,Comparator도 제공하지 않으면 런타임 오류가 발생한다.
결론
실무에서는 객체 정렬 기준이 고정적이지 않고 동적으로 바뀌는 경우가 많다.
예를 들면 API로부터 받은 데이터를 날짜 순, 이름 순, 특정 속성 값 기준 등으로 동적으로 바꿔가며 정렬해야 할 때 Comparator를 활용할 수 있다.
TIP. 아래와 같이 람다와 같이 사용하면 더욱 효과적으로 사용할 수 있다.
List<MyUser> users = Arrays.asList(
new MyUser("user1", 30),
new MyUser("user2", 20),
new MyUser("user3", 25)
);
// 나이로 정렬 후 이름으로 정렬
users.sort(Comparator.comparingInt(MyUser::getAge)
.thenComparing(MyUser::getId));
System.out.println(users);