끄적이기

[TIL - 9] Comparable, Comparator 완전 이해

배씌 2025. 3. 12. 10:47

지난 Comparable, Comparator 기록해서 부족한 점이 있었고, 스스로도 제대로 이해하지 못한것 같아 이번에 확실하게 이해한 내용을 바탕으로 기록해두겠다.

 

지난 번에 단순히 두 인터페이스의 차이는

  • Comparable -> compareTo 메서드 구현
  • Comparator -> compare 메서드 구현

이라고만 기억을 했었는데, 근본적으로 두 인터페이스의 역할에 대해 고민을 해보다, 사람의 언어로 생각해보니 이해하는데 더 도움이 됐다.

 

말 그대로, 

  • Comparable : 비교 가능한
  • Comparator : 비교자

우리가 실생활에서 무언가를 비교하려할 때 분명히 비교 기준이 필요하다. 두 물체를 무게로 비교할 것인지, 크기로 비교할 것인지 등이 있겠다. 그런데, 이 두 물체가 비교가 가능한 것이라는 것은 어떻게 알고 있는가?

 

간단하다. 우리는 이 두 물체가 비교가능한 물체라는 것을 알고 있기 때문이다. 이 생각을 머리에 두고 위의 두 인터페이스를 살펴보자.


Comparable

우선 Comparable 부터 살펴보겠다.

우리가 실생활에서 비교할 수 있는 무언가를 알 수 있듯이, 프로그래밍에서도 비교가능한, 또는 비교하고 싶은 객체에 Comparable을 선언해주면 된다. 대표적인 예로, (Integer, String, Float, ...) 등 다양한 타입들이 있다.

 

Integer를 살펴보면 아래와 같이 Comparable 을 구현하고 있는 것을 볼 수 있다.

@jdk.internal.ValueBased
public final class Integer extends Number
        implements Comparable<Integer>, Constable, ConstantDesc {
    /**
     * A constant holding the minimum value an {@code int} can
     * have, -2<sup>31</sup>.
     */
    @Native public static final int   MIN_VALUE = 0x80000000;
	
    ...

 

그렇기 때문에, Comparable 인터페이스를 구현하는 compareTo 메서드를 볼 수 있다. 이때 compare 라는 메서드를 사용하는데, 이는 Integer 내부에 static 으로 정의된 메서드이다. (Comparator 인터페이스와는 무관 !) 이를 몰라서, 많이 헷갈렸는데 확실히 기억하자.

public int compareTo(Integer anotherInteger) {
    return compare(this.value, anotherInteger.value);
}


public static int compare(int x, int y) {
    return (x < y) ? -1 : ((x == y) ? 0 : 1);
}

 

예시로, Student 객체를 비교하려 하는데, 나이를 기준으로 삼고 싶다면 아래처럼 작성할 수 있을 것이다.

public class Student implements Comparable<Student> {
    int age;
    int classNumber;

    Student(int age, int classNumber) {
        this.age = age;
        this.classNumber = classNumber;
    }

    @Override
    public int compareTo(Student o) {
        // 자기 자신의 age가 o의 age 보다 크다면 양수
        if(this.age > o.age)
            return 1;
            // 자기 자신의 age와 o의 age가 같다면 0
        else if(this.age == o.age)
            return 0;
            // 자기 자신의 age가 o의 age보다 작다면 음수
        else
            return -1;
    }
}

 

정리하자면, Comparable 은 객체나 클래스가 비교가 가능한 녀석이라고 표시하고, 그 비교 기준을 만들어주는 역할이다.


Comparator

Comparator 는 비교자이다. 우선, Comparator 가 어떻게 정의되어있는지 살펴보겠다. 아래를 보면 한가지 의문점이 든다. 살펴보면 알겠지만, 메서드가 1개가 아닌데도 함수형 인터페이스(@FunctionaInterface) 어노테이션이 붙어있다.

 

이는 함수형 인터페이스의 정의가 단 하나의 "추상 메서드"만 가진 인터페이스를 의미하기 때문이다. 실제로, compare 을 제외한 나머지 메서드는 default 메서드로 정의되어 있고, 아래의 equals 메서드 역시 Object 의 메서드 이기 때문에 이미 구현이 되어 있는 상태이다.

@FunctionalInterface
public interface Comparator<T> {
	
    ...
    
    int compare(T o1, T o2);
    
    ...
    
    boolean equals(Object obj);
    
    ...

 

 

그렇다면 이 Comparator 는 어디에 사용할까?

아래와 같이 sort 함수를 오버라이딩 하는 상황을 예시로 들어보겠다.

@Override
public void sort(Comparator<? super T> comparator) {
    
}

 

만약, 이 sort 함수를 Integer 타입의 List에서 사용한다고 가정하자. 쉽게 말해 아래처럼 사용하겠다는 것이다.

List<Integer> list = new ArrayList<>();
list.sort(  );

 

이때, sort의 매개변수로 Comparator 가 들어와야 한다는 것인데, 어떻게 해야할까?

우선, Comparator 는 compare 메서드를 구현해야 한다는 것을 알고 있다. 그리고 함수형 인터페이스는 매개변수로 사용가능하다.

Comparator<Integer> comparator = new Comparator<>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o1.compareTo(o2);
    }
};
list.sort(comparator);

 

이때, 익명 클래스는 람다 표현식을 활용하여 축약하여 작성할 수 있다. 줄이면 아래와 같을 것이다.

Comparator<Integer> comparator = (o1, o2) -> o1.compareTo(o2);
// Comparator<Integer> comparator = Integer::compareTo;
// list.sort(comparator);

list.sort(Integer::compareTo);

 

이렇게 되면, sort의 매개변수로 비교 기준인 Integer 클래스의 compareTo 값이 들어오는 것이다. (이때 compareTo 메서드는 Integer 클래스의 메서드이다.)

 

해당 값을 활용하여 아래와 같이 sort 함수를 구현할 수 있다.

@Override
public void sort(Comparator<? super T> comparator) {
    Node a = new Node(1);
    Node b = new Node(2);
    if(comparator.compare((T)a.getData(), (T)b.getData()) > 0) {
        

    }
}

 

정리하자면, Comparator 는 비교자로써, 익명 클래스와 함수형 인터페이스의 성질을 이용해 두 객체를 어떤 기준으로 비교할 것인지 정의하는 역할로 볼 수 있다.