Published 2022. 2. 22. 16:26
반응형

Java Comparator, Comparable

Comparator와 Comparable이란?

  • 객체 정렬에 필요한 메서드를 제공하는 인터페이스

Comparable

  • 객체의 기본 정렬 메서드를 제공하는 인터페이스
public interface Comparable {
    int compareTo(Object o); // 주어진 객체(o)를 자신과 비교
}

Comparator

  • 기본 정렬 기준 외에 다른 기준으로 정렬할 때 사용하는 인터페이스
public interface Comparator {
    int compare(Object o1, Object o2);
}

기본 정렬과 기본 정렬이 아닌 것이 뭘까?

기본 정렬이란 말 그대로 디폴트 정렬을 의미합니다. 특별히 정렬 기준을 설정하지 않는다면,
기본 정렬 기준을 사용하겠다는 의미입니다. String 클래스 같은 경우 Arrays.sort()를 사용할 경우
오름차순 정렬이 되는데 이는 String은 Comparable의 compareTo()를 구현하고 있기 때문입니다.

아래처럼 테스트를 해보면 확인할 수 있습니다.

@Test
void compare() {
    String[] strArr = {"b", "a", "c"};
    Arrays.sort(strArr);

    assertThat(strArr).isEqualTo(new String[]{"a", "b", "c"});
}

Integer클래스로 조금 더 살표 보겠습니다.
자신의 값이 anotherInteger보다 작으면 -1을 반환하고, 같으면 0, 크면 1을 반환합니다.
이런 식으로 기본 정렬을 클래스에 구현해 놓으면 Arrays.sort()와 같은 메서드 내에서 이를 활용하여 정렬을 하는 것입니다.
즉, sort() 내부에서는 compareTo()1을 반환한다면 오름차순으로 정렬을 하겠죠?
하지만 sort()가 자신이 anotherInteger 클 때 1을 반환하면 오름차순 정렬을 하는 것은 단지 API 사용법일 뿐입니다.
만약 반대로 자신이 anotherInteger 보다 작을 때 1을 반환하게 compareTo()를 작성한다면 sort()내림차순으로 정렬 하게되는 것입니다.

즉, 이 규칙으로 구현을 하게 되면, sort()와 같은 메서드들은 오름차순으로 정렬을 하게 되고,
기본 정렬을 내림 차순으로 하고 싶다면, 반대로 로직을 작성하면 됩니다.

public final class Integer extends Number implements Comparable<Integer> {
        public int compareTo(Integer anotherInteger) {
            return (this.value < anotherInteger) ? -1 : ((this.value == anotherInteger) ? 0 : 1);
    }
}

Compare(Object o1, Obejct o2)의 파라미터로 뭘 어떻게 정렬한다는 거지?

음.. 뭔가 바보 같은 의문이 들었습니다.
먼저 헷갈리지 않았던 compareTo(Object o)에 대해 알아보겠씁니다.
대략 아래와 같이 사용하고 있습니다. 실제 구현된 코드를 일부만 발췌해 왔습니다.

private static void binarySort(Object[] a, int lo, int hi, int start) {
    assert lo <= start && start <= hi;
    if (start == lo)
        start++;
        for ( ; start < hi; start++) {
            Comparable pivot = (Comparable) a[start];

            // Set left (and right) to the index where a[start] (pivot) belongs
            int left = lo;
            int right = start;
            assert left <= right;
            /*
             * Invariants:
             *   pivot >= all in [lo, left).
             *   pivot <  all in [right, start).
             */
            while (left < right) {
                int mid = (left + right) >>> 1;
                if (pivot.compareTo(a[mid]) < 0) /////////////////////// 이 부분이 c.compare()로 바뀐다.
                    right = mid;
                else
                    left = mid + 1;
            }
            assert left == right;
....

Object[] a가 정렬될 대상입니다
``a의 값을 꺼내어 pivot에 담고, pivot(자기 자신)과 나머지 리스트와 비교를 하는 while문이 존재합니다.
자가 자신의 compareTo()의 파라미터로 비교될 대상을 받아 처리하게 됩니다.
compareTo()는 대략 이런 식으로 사용되고 있습니다.

그러면 compare()는 기준이 뭐가 되는거지? compareTo()는 자기 자신이랑 나머지 배열, 리스트에 있는 데이터 들과 비교하는 것인데...

기준이 두개가 되는건가?? 라는 생각이 들었습니다. 아주 바보같은 생각이었습니다.
단순하게 sort(Object[] a, Comparator c)와 같이 넘기면, 정렬될 데이터는 당연히 Object[] a이고, 기준이 되는 것은 Comparator c입니다.

그럼 사용하는 코드는 대략 아래와 같이 나올 수 있습니다.
위에 compareTo()를 사용했던 것과 거의 동일하지만 Comparator 파라미터만 추가되고
이 파라미터에 있는 compare()를 활용하게 됩니다. 딱 한 군데 코드만 바뀌면 됩니다.
이런 식으로 사용되는 것입니다. 아주 바보같은 생각이었습니다.. ㅜ.ㅜ

private static <T> void binarySort(T[] a, int lo, int hi, int start, Comparator<? super T> c) {
    ...
    if (c.compare(pivot, a[mid]) < 0) {
        ...
    }
    ...
}

String의 정렬 기준을 바꿔보자.(Comparator)

String에는 Comparable이 구현되어 있었습니다.
그렇다면 다른 정렬 기준을 만들고 싶을 때는 어떻게 해야 할까요? 이 때 Comparator를 사용하면 됩니다.

먼저 String에서도 Comparator를 구현해 놓은 코드를 살펴 보겠습니다.
상세한 구현 코드는 살표보지 않겠습니다. 아래 코드는 String이 제공하는 기본 정렬 이외의 정렬 기준을 제공하기 위해 구현한
Comparator입니다.

public static final Comparator<String> CASE_INSENSITIVE_ORDER
                                         = new CaseInsensitiveComparator();

이를 활용하면 대소문자 구분없이 정렬을 할 수 있습니다.

@Test
void compare() {
    String[] strArr = {"B", "a", "c"};
    Arrays.sort(strArr, String.CASE_INSENSITIVE_ORDER);

    assertThat(strArr).isEqualTo(new String[]{"a", "B", "c"});
}

이번에는 직접 Comparator를 구현해서 정렬 기준을 만들어 보겠습니다.
정렬 기준은 역순 정렬로 해보겠습니다.

class Descending implements Comparator {

    @Override
    public int compare(Object o1, Object o2) {
        if (o1 instanceof Comparable && o2 instanceof Comparable) {
            Comparable c1 = (Comparable) o1;
            Comparable c2 = (Comparable) o2;
            return c1.compareTo(c2) * -1;
        }
        return -1;
    }
}

파라미터로 넘오온 객체의 compareTo()는 오름차순 정렬이 되어있기 때문에 역순 정렬은
-1만 곱해주면 됩니다.

반응형
복사했습니다!