Iterator, Iterable, Comparator, Comparable
- 이번 포스팅에서는 Java에서 자주 사용되는 Iterator, Iterable, Comparator, Comparable의 개념과 사용법을 정리합니다.
- 이들은 컬렉션과 객체 비교, 반복 처리에서 필수적으로 쓰이는 중요한 인터페이스들입니다.
 
 
- Iterator와 Iterable은 컬렉션을 순회하는 방법을 제공하며, Comparator와 Comparable은 객체 간의 비교를 위해 사용됩니다.
 
1. Iterator, Iterable
1.1 Iterator 인터페이스
- Iterator는 컬렉션 요소들을 순차적으로 탐색하고 제거할 수 있는 인터페이스입니다.
- Iterator는 컬렉션의 요소를 하나씩 가져오며, 다음 요소가 있는지 확인하고, 추가적으로 현재 요소를 삭제할 수 있는 기능을 제공합니다.
 
java.util 패키지에 소속되어 있습니다. 
 
- 주요 메서드
- 추상 메서드 (반드시 구현해야함)
boolean hasNext() : 컬렉션에 남아 있는 요소가 있는지 확인합니다. 
E next() : 다음 요소를 반환합니다. 
 
- default 메서드 (필요시 구현)
default void remove() : 현재 요소를 컬렉션에서 제거합니다. 
 
 
package java.util;
public interface Iterator<E> {
	boolean hasNext();
    E next();
    
    default void remove() {
        throw new UnsupportedOperationException("remove");
    }
}
Iterator 활용 예시
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class IteratorExample {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("A");
        list.add("B");
        list.add("C");
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            String element = iterator.next();
            System.out.println(element);
            
            if (element.equals("B")) {
                iterator.remove(); 
            }
        }
        System.out.println("After removing B: " + list); 
    }
}
특징
Iterator는 컬렉션의 요소를 순차적으로 탐색할 때 사용되며, 컬렉션의 요소를 안전하게 제거할 수 있는 메커니즘도 제공합니다.
- 주로 컬렉션에서 특정 요소를 삭제할 때 유용하며, 
for-each와는 다르게 remove() 메서드를 통해 요소를 제거할 수 있습니다. 
 
- Generic Type을 활용하기 때문에 사용시 객체의 타입을 지정해주어야합니다.
 
fail-fast: Iterator는 컬렉션이 외부 혹은 다른 스레드에서 수정될 경우 ConcurrentModificationException을 발생시키며, 이는 반복자 사용 중 컬렉션이 외부에서 변경되는 것을 방지합니다. 
1.2 Iterable 인터페이스
- Iterable은 반복자(Iterator)를 제공하는 컬렉션의 최상위 인터페이스입니다.
Iterable을 구현한 클래스는 Iterator를 제공할 수 있으며, for-each 루프와 함께 사용됩니다. 
 
- 즉, 
Iterable은 컬렉션이 순회될 수 있음을 의미하며, 이를 통해 컬렉션의 요소들을 하나씩 탐색할 수 있습니다.
- 참고로 
java.lang 패키지 소속이기 때문에, 별도의 import 없이 사용 가능합니다.  
 
- 주요 메서드 
iterator() : Iterator 객체를 반환하여 반복 작업을 수행할 수 있게 합니다. 
 
package java.lang;
public interface Iterable<T> {
	Iterator<T> iterator();
    
    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
}
Iterable 예시
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class IterableExample {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("A");
        list.add("B");
        list.add("C");
        
        for (String element : list) {
            System.out.println(element);
        }
        
        
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}
특징
Iterable 인터페이스는 주로 for-each 구문에서 사용됩니다. 
- 컬렉션의 각 요소를 순차적으로 탐색하기 위해 
iterator() 메서드를 호출하여 반복자를 생성합니다. 
 
- 모든 
Collection은 기본적으로 Iterable을 구현하고 있어, for-each 루프를 사용할 수 있습니다. 
1.3 Iterator와 Iterable의 차이점
- Iterable은 컬렉션이 반복할 수 있음을 나타내는 인터페이스입니다.
iterator() 메서드를 통해 Iterator 객체를 반환합니다. 
 
- Iterator는 컬렉션의 요소들을 실제로 탐색하는 인터페이스입니다. 
hasNext(), next(), remove()와 같은 메서드를 통해 컬렉션의 요소를 탐색하고 조작할 수 있습니다. 
 
2. Comparator, Comparable
2.1 Comparable 인터페이스
Comparable 인터페이스는 객체 간의 자연 순서(Natural Ordering)를 정의하는 데 사용됩니다.
- 주로 정렬을 위해 사용되며, 객체 자체에 비교 방법을 정의합니다.
 
compareTo() 메서드를 구현하여 두 객체의 비교 결과를 반환하며, 이를 통해 객체들이 어떻게 정렬될지를 결정합니다. 
 
- 주요 메서드
int compareTo(T o) : 객체와 전달된 객체를 비교하여 다음과 같은 값을 반환합니다
- 음수: 현재 객체가 전달된 객체보다 작을 때
 
- 0: 같을 때
 
- 양수: 현재 객체가 전달된 객체보다 클 때
 
 
 
package java.lang;
public interface Comparable<T> {
	public int compareTo(T o);
}
Comparable 사용 예시
import java.util.Arrays;
public class Person implements Comparable<Person> {
    private String name;
    private int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public int compareTo(Person other) {
        return this.age - other.age;  
    }
    @Override
    public String toString() {
        return name + " (" + age + ")";
    }
    public static void main(String[] args) {
        Person[] people = {
            new Person("Alice", 30),
            new Person("Bob", 25),
            new Person("Charlie", 35)
        };
        Arrays.sort(people);  
        System.out.println(Arrays.toString(people));
        
    }
}
특징
- 자연 순서(Natural Ordering): 
Comparable은 클래스의 자연 순서를 정의합니다. 
- 예를 들어, 
Integer, String 등의 클래스는 이미 Comparable을 구현하여, 숫자나 알파벳 순서로 정렬됩니다. 
 
- 정렬 기준 내장: 
Comparable을 사용하면 객체 자체에 정렬 기준을 정의하기 때문에 별도의 정렬 방법을 지정하지 않아도 sort() 메서드 등을 사용할 때 자동으로 정렬됩니다. 
- 1차원적인 정렬 기준: 
Comparable은 단일 기준의 정렬만 지원합니다. 
- 만약 여러 기준으로 정렬을 해야 할 경우에는 Comparator가 필요합니다.
 
 
2.2 Comparator 인터페이스
Comparator 인터페이스는 외부에서 객체 간의 비교 방법을 정의하는 데 사용됩니다.
- 객체 자체에 비교 로직을 넣지 않고, 별도의 클래스나 람다 표현식을 통해 비교 방법을 제공할 수 있습니다.
 
Comparator는 주로 여러 기준으로 객체를 정렬해야 할 때 사용됩니다. 
 
- 주요 메서드
int compare(T o1, T o2) : 두 객체를 비교하여 다음과 같은 값을 반환합니다:
- 음수: 첫 번째 객체가 두 번째 객체보다 작을 때
 
- 0: 같을 때
 
- 양수: 첫 번째 객체가 두 번째 객체보다 클 때
 
 
 
package java.util;
@FunctionalInterface
public interface Comparator<T> {
	int compare(T o1, T o2);
    boolean equals(Object obj);
    
    default Comparator<T> reversed() {
        return Collections.reverseOrder(this);
    }
    default Comparator<T> thenComparing(Comparator<? super T> other) {
        Objects.requireNonNull(other);
        return (Comparator<T> & Serializable) (c1, c2) -> {
            int res = compare(c1, c2);
            return (res != 0) ? res : other.compare(c1, c2);
        };
    }
    
    public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() {
        return Collections.reverseOrder();
    }
    public static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {
        return (Comparator<T>) Comparators.NaturalOrderComparator.INSTANCE;
    }
    public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
            Function<? super T, ? extends U> keyExtractor)
    {
        Objects.requireNonNull(keyExtractor);
        return (Comparator<T> & Serializable)
            (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
    }
    
}
Comparator 사용 예시
import java.util.Arrays;
import java.util.Comparator;
public class Person {
    private String name;
    private int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return name + " (" + age + ")";
    }
    public static void main(String[] args) {
        Person[] people = {
            new Person("Alice", 30),
            new Person("Bob", 25),
            new Person("Charlie", 35)
        };
        
        Comparator<Person> nameComparator = Comparator.comparing(person -> person.name);
        Arrays.sort(people, nameComparator);
        System.out.println("이름순 정렬: " + Arrays.toString(people));
        
        Comparator<Person> ageComparator = Comparator.comparingInt(person -> person.age);
        Arrays.sort(people, ageComparator);
        System.out.println("나이순 정렬: " + Arrays.toString(people));
    }
}
특징
- 유연한 정렬: 
Comparator는 객체 외부에서 정렬 기준을 정의할 수 있으므로, 객체 하나에 여러 정렬 기준을 적용할 수 있습니다. (예: 나이순, 이름순 등) 
- 여러 기준을 결합 가능: 
thenComparing() 메서드를 사용하여 여러 정렬 기준을 결합할 수 있습니다.
- 예를 들어, 먼저 나이순으로 정렬하고, 나이가 같으면 이름순으로 정렬하는 식의 다중 정렬 기준을 적용할 수 있습니다.
 
 
Comparator 다중 기준 사용 예시
import java.util.Arrays;
import java.util.Comparator;
public class Person {
    private String name;
    private int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return name + " (" + age + ")";
    }
    public static void main(String[] args) {
        Person[] people = {
            new Person("Alice", 30),
            new Person("Bob", 25),
            new Person("Charlie", 25)
        };
        
        Comparator<Person> ageThenNameComparator = Comparator.comparingInt((Person person) -> person.age)
                                                             .thenComparing(person -> person.name);
        Arrays.sort(people, ageThenNameComparator);
        System.out.println("나이순, 이름순 정렬: " + Arrays.toString(people));
        
    }
}
2.3 Comparable와 Comparator의 차이점과 활용 사례
차이점
- 내부 정렬 vs 외부 정렬
Comparable은 객체 내부에 정렬 기준을 정의하는 반면, Comparator는 객체 외부에서 정렬 기준을 정의합니다. 
- 즉, 
Comparable은 객체가 스스로 자신을 어떻게 정렬할지를 알고 있지만, Comparator는 외부에서 정렬 방법을 제공합니다. 
 
- 단일 기준 vs 다중 기준
Comparable은 단일 정렬 기준을 사용해야 하지만, Comparator는 여러 정렬 기준을 조합하여 사용할 수 있습니다. 
 
- 정렬 방법의 유연성
Comparable을 사용하면 객체 자체에 정렬 로직이 내장되므로 정렬 방법을 변경하기 어렵지만, Comparator는 필요에 따라 여러 정렬 기준을 제공할 수 있는 유연성을 가집니다. 
 
 | Comparable | Comparator | 
|---|
| 정렬 기준 | 객체 자체에 정의된 기본 정렬 기준 사용 | 외부에서 제공된 정렬 기준 사용 | 
| 적용 방식 | compareTo() 메서드 구현 | compare() 메서드 구현 또는 람다식 사용 | 
| 단일 vs 다중 기준 | 단일 기준 정렬만 가능 | 다중 기준 정렬 가능 (thenComparing()) | 
| 유연성 | 정렬 기준 변경이 어렵다 | 여러 정렬 기준을 유연하게 설정 가능 | 
활용 사례
Comparable 활용
- 기본적인 정렬이 필요한 경우
 
- 특히 동일한 방식으로 정렬이 반복적으로 이루어질 경우 
Comparable을 사용합니다. 
- 예를 들어, 
String, Integer와 같은 클래스는 Comparable을 구현하고 있습니다. 
 
Comparator 활용
- 상황에 따라 다양한 기준으로 정렬을 해야 할 경우
 
- 특히 여러 기준을 결합하거나 다양한 정렬 방식이 필요한 경우 
Comparator를 사용합니다. 
 
2.4 Comparator와 Comparable 사용 시 유의사항
- 자연 순서 vs 사용자 정의 순서: 기본적으로 
Comparable로 정의된 자연 순서와, Comparator로 외부에서 제공되는 사용자 정의 순서 간의 차이를 이해하고, 상황에 따라 적절하게 선택해야 합니다. 
- 동일성 체크: 
compareTo() 또는 compare()에서 반환값이 0일 경우, 두 객체는 동일한 것으로 간주됩니다. 이때, equals() 메서드도 동일한 결과를 반환하는지 확인하는 것이 좋습니다. 
- Comparator의 성능: 복잡한 다중 기준 정렬 시, 성능에 영향을 줄 수 있으므로, 필요에 따라 캐싱이나 미리 계산된 값을 사용하는 등의 최적화가 필요할 수 있습니다.
 
마무리
- 이번 포스팅에서는 Iterator, Iterable, Comparator, Comparable이라는 네 가지 주요 인터페이스에 대해 살펴보았습니다.
Iterator와 Iterable은 컬렉션 요소를 탐색하고 순회하는 기능을 제공하며, 컬렉션의 요소를 안전하게 처리할 수 있도록 합니다. 
Comparable과 Comparator는 객체 간의 비교 및 정렬을 위한 도구로, 자연 순서에 따른 정렬과 외부에서 제공되는 사용자 정의 정렬을 가능하게 합니다.