JAVA

Comparable 인터페이스 너 뭐야!

개발자성장기 2025. 2. 26. 20:07
반응형

 

 

 

우테코에서 출석 미션을 하는데 Comparable을 사용할 일이 많았다.  

페어프로그래밍 하면서 페어에게 설명해주고 싶었는데 항상 관성적으로 써오던 인터페이스라 

그냥 '객체를 서로 비교할 때 사용하시면 좋습니다'라는 말 밖에 생각나지 않았다. 

그렇다.  Comaprable에 대해 제대로 공부해본 적이 없었다.  

그래서 지금 하고자 한다.  

 

1. Comparable은 무엇인가?

Comaprable은 이 객체와 다른 객체를 비교할 수 있다는 것을 의미하는 인터페이스이다.  

`java.lang.Comparable<T>` 형태로 존재하며, T라는 제네릭 타입을 통해 "어떤 타입을 비교할 건지"를 지정해준다.

자바 API 문서를 확인해보면, Comparable은 다음과 같이 정의되어 있다.  

 

 

 

Comparable 주석을 해석해보면 아래와 같다.

 

이 인터페이스는 이를 구현하는 각 클래스의 객체들에 대한 전체 순서(total ordering) 를 부여한다.

이 순서는 해당 클래스의 자연 순서(natural ordering) 라고 하며, 클래스의 compareTo 메서드는 자연 비교 메서드(natural comparison method) 라고 한다.

 


❓전체 순서(Total Ordering)
전체 순서(total ordering) 란, 집합 내 모든 요소들 간에 일관된 비교 기준이 존재하는 것을 의미한다.
즉, 집합 안의 어떤 두 요소라도 비교가 가능하고, 항상 "작다(<), 크다(>), 같다(=)"라는 관계가 성립해야 한다.
예를 들어, 숫자 집합 {3, 1, 4, 2}를 오름차순으로 정렬하면 {1, 2, 3, 4}가 되는데,
여기서 모든 숫자 간의 비교 기준이 "작다(<) 또는 크다(>)" 로 확실하게 정해져 있으므로 전체 순서를 가진다고 볼 수 있다.

❓자연 순서(Natural Ordering)
자연 순서(natural ordering) 는 특정 클래스가 기본적으로 따르는 정렬 기준을 의미한다.
예를 들어, Integer 클래스의 자연 순서는 숫자의 크기순(오름차순) 이고, String 클래스의 자연 순서는 사전순 이다. 이처럼, 자연 순서는 그 클래스의 compareTo 메서드가 정의하는 순서를 따르게 된다.

 

 

 

2. 왜 필요한가? 

우리가 나이순으로 회원 리스트를 나열하고 싶거나 쇼핑몰 상품을 가격 순으로 보고 싶다는 등 객체들을 "정렬"하고 싶을 때 `Collections.sort()` 메서드나 `Arrays.sort()`메서드를 사용할 수 있는데 이때 `Comparable`을 구현해두면 어떤 기준으로 정렬해야 하는지 자바가 알 수 있게 된다.  

 

쉽게 말해, "내가 원하는 기준으로 객체를 비교/정렬하려면, `Comparable`을 구현해 `compareTo`를 작성하면 된다." 라고 기억하면 된다. 

이펙티브 자바(Item 14)에서는  아래와 같이 표현한다.

좁쌀만 한 노력으로 코끼리만 한 큰 효과를 얻는 것이다.


3. 예시

간단한 Person 객체이다.
public class Person implements Comparable<Person> {
    private String name;
    private int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
    
    @Override
    public int compareTo(Person other) {
        return this.age - other.age;
    }
}

 

위 예시에는 `Person`클래스가 `Comparable<Person>`을 implements했고, `compareTo(Person other)` 메서드를 오버라이딩 했다.  그 결과 "나이"가 비교 기준이된다. 

 

만약 여기서 다른 기준인 `name`으로 정렬하고 싶다면 아래처럼 해주면 된다.

@Override
public int compareTo(Person other) {
    return this.name.compareTo(other.name);
}

문자열끼리 비교하면 compareTo를 바로 사용할 수 있다. (객체도 가능)

이렇게 바꿔주기만 하면,  `Collections.sort()` 호출 시 이름 사전 순으로 정렬된다.  

 

 

 

4. Comparable vs   Comparator

사실 이번 기회에 확실히 둘의 차이를 알게되었다. 

  • `Comparable`은 "클래스 자체"에 비교 기준을 정의해두는 방식이다.  
    • 즉, 어떤 클래스가 "나를 어떻게 정렬할지"를 스스로 알고 있게 만든다.
    • 아주 쉽게 말하면 객체가 스스로 정렬 기준을 정한다고 생각하면 좋다.
  • `Comparator`는 비교 로직을 따라 분리해둘 때 사용하는 인터페이스이다.
    • 즉,  "정렬 방법을 다르게 하고 싶을 때 따로 만들게!" (정렬 기준을 외부에서 따로 정함)
    • 예를 들어, "나이 순 정렬", "이름 순 정렬" 등 여러 가지 기준을 클래스 외부에서 정의해야 한다면, `Comparator`를 이용해 별도의 클래스로 만들어줄 수 있다. 
    • 쉬운 예로 들자면 기존에 Comparable을 통해서 사람을 나이 순으로 정렬했지만 이름순으로도 정렬하고 싶다면? 그때 바로 Comparator을 사용하면 된다. 
Comparator<Person> nameComparator = Comparator.comparing(person -> person.name);

people.sort(nameComparator);  

System.out.println(people);
// [Alice (30), Bob (25), Charlie (35)]

 

 

 

5.  Comparator 인터페이스

자바 8에서 `Comparator` 인터페이스가 일련의 비교자 생성 메서드와 메서드 연쇄방식으로 비교자를 생성할 수 있게 되었다.

  • 비교자들은 `compareTo` 메서드를 구현하는데 활용될 수 있다.
    • 이 방식은 간결하지만 약간의 성능 저하가 발생한다.
    • 자바의 정적 임포트 기능을 활용하면 정적 비교자 생성 메서드들을 그 이름만으로 사용할 수 있어 코드가 훨씬 깔끔해진다.
  • `Comparator`는 `comparingInt`와 `thenComparingInt`등의 숫자용 기본 타입을 커버하는 보조 생성 메서드들을 가지고 있다.
  • `comparing`와 `thenComparing`이란 객체 참조용 비교자 생성 메서드 또한 가지고 있다. 
private static final Comparator<PhoneNumber> COMPARATOR =
            Comparator.comparingInt((PhoneNumber phoneNumber) -> phoneNumber.areaCode)
                    .thenComparingInt(phoneNumber -> phoneNumber.prefix)
                    .thenComparingInt(phoneNumber -> phoneNumber.lineNum);

public int compareTo(PhoneNumber phoneNumber) {
	    return COMPARATOR.compare(this, phoneNumber);
}
  • 위 코드는 비교자 생성 메서드 2개를 이용해 비교자를 생성한다.
    • 첫 번째는 comparingInt 두 번째는 thenCompaingInt이다.
  • comparingInt는 객체 참조를 int 타입 키에 매핑하는 키 추출 함수를 인수로 받아 그 키를 기준으로 순서를 정하는 비교자를 반환하는 정적 메소드이다.
  • thenComparingInt는 Comparator의 인스턴스 메서드로, int 키 추출 함수를 입력받아 다시 비교자를 반환한다.
    • thenComparingInt는 연달아 호출이 가능하다.
    • 자바의 타입 추론 능력으로 인해 호출할 때 타입을 명시하지 않는다.

 

 

한걸음 더

compareTo 메서드의 일반 규약

📚 Reference

- https://docs.oracle.com/javase/8/docs/api/java/lang/Comparable.html

반응형