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는 객체 간의 비교 및 정렬을 위한 도구로, 자연 순서에 따른 정렬과 외부에서 제공되는 사용자 정의 정렬을 가능하게 합니다.