[Java 심화] Iterator, Iterable, Comparator, Comparable 정리

Kyung Jae, Cheong·2024년 10월 12일
0
post-thumbnail

Iterator, Iterable, Comparator, Comparable

  • 이번 포스팅에서는 Java에서 자주 사용되는 Iterator, Iterable, Comparator, Comparable의 개념과 사용법을 정리합니다.
    • 이들은 컬렉션과 객체 비교, 반복 처리에서 필수적으로 쓰이는 중요한 인터페이스들입니다.
  • IteratorIterable은 컬렉션을 순회하는 방법을 제공하며, ComparatorComparable은 객체 간의 비교를 위해 사용됩니다.

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(); // "B" 삭제
            }
        }

        System.out.println("After removing B: " + list); // 출력: [A, C]
    }
}

특징

  • 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");

        // Iterable을 사용하는 for-each 문
        for (String element : list) {
            System.out.println(element);
        }
        
        // 명시적으로 Iterator를 사용하는 방식
        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);  // Comparable에 정의된 순서대로 정렬
        System.out.println(Arrays.toString(people));
        // 출력: [Bob (25), Alice (30), Charlie (35)]
    }
}

특징

  • 자연 순서(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 사용)
        Comparator<Person> nameComparator = Comparator.comparing(person -> person.name);
        Arrays.sort(people, nameComparator);
        System.out.println("이름순 정렬: " + Arrays.toString(people));

        // 나이순으로 정렬 (Comparator 사용)
        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));
        // 출력: [Bob (25), Charlie (25), Alice (30)]
    }
}

2.3 Comparable와 Comparator의 차이점과 활용 사례

차이점

  • 내부 정렬 vs 외부 정렬
    • Comparable은 객체 내부에 정렬 기준을 정의하는 반면, Comparator는 객체 외부에서 정렬 기준을 정의합니다.
    • 즉, Comparable은 객체가 스스로 자신을 어떻게 정렬할지를 알고 있지만, Comparator는 외부에서 정렬 방법을 제공합니다.
  • 단일 기준 vs 다중 기준
    • Comparable은 단일 정렬 기준을 사용해야 하지만, Comparator는 여러 정렬 기준을 조합하여 사용할 수 있습니다.
  • 정렬 방법의 유연성
    • Comparable을 사용하면 객체 자체에 정렬 로직이 내장되므로 정렬 방법을 변경하기 어렵지만, Comparator는 필요에 따라 여러 정렬 기준을 제공할 수 있는 유연성을 가집니다.
ComparableComparator
정렬 기준객체 자체에 정의된 기본 정렬 기준 사용외부에서 제공된 정렬 기준 사용
적용 방식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이라는 네 가지 주요 인터페이스에 대해 살펴보았습니다.
    • IteratorIterable은 컬렉션 요소를 탐색하고 순회하는 기능을 제공하며, 컬렉션의 요소를 안전하게 처리할 수 있도록 합니다.
    • ComparableComparator는 객체 간의 비교 및 정렬을 위한 도구로, 자연 순서에 따른 정렬과 외부에서 제공되는 사용자 정의 정렬을 가능하게 합니다.
profile
일 때문에 포스팅은 잠시 쉬어요 ㅠ 바쁘다 바빠 모두들 화이팅! // Machine Learning (AI) Engineer & BackEnd Engineer (Entry)

0개의 댓글