유일무이한 메서드 compareTo()를 가지고 있는 Comparable 인터페이스에 대해서 알아보자.
public interface Comparable<T> {
public int compareTo(T o);
}
compareTo()는 단순 동치성 비교에 더해 순서까지 비교할 수 있으며, 제네릭 하다.
Comparable을 구현했다는 것은 그 클래스의 인스턴스들에는 자연적인 순서가 있음을 뜻해 객체들의 배열을 정렬할 수 있다. Arrays.sort()는 자동으로 Comparalbe에 구현되어 있는 compareTo()를 호출해서 사용한다.
자바 플랫폼 라이브러리의 모든 값 클래스와 열거 타입은 Comparable을 구현하고 있다.
그러니 알파벳, 숫자, 연대 같이 순서가 명확한 값 클래스를 작성한다면 반드시 Comparable 인터페이스를 구현하자.
규약
- 객체가 매개변수로 주어진 객체보다 작으면 음의정수(-1) 같으면 0, 크면 양의 정수(+1)를 반환한다.
- 객체와 비교할 수 없는 타입의 객체가 매개변수로 주어진다면 ClassCastException을 반환한다.
x.compareTo(y) == -y.compareTo(x)
- 두 객체 참조의 순서를 바꿔 비교해도 같은 결과가 나와야 한다.
- 즉, 첫 번째 객체가 두 번째 객체보다 작으면 두 번째가 첫 번째보다 커야 한다.
x.compareTo(y) >0 && y.compareTo(z) >0 이면 x.compareTo(z) >0이다
- 추이성
- (첫 번째가 두 번째보다 크고) (두 번째가 세 번째보다 크면) (첫 번째는 세 번째보다 커야 한다.)
x.compareTo(y) == 0 이면 x.compareTo(z) == y.compareTo(z)이다
- 크기가 같은 객체들끼리는 어떤 객체와 비교하더라도 항상 같아야 한다.
이렇게 3개의 규약은 compareTo 메서드로 수행하는 동치성 검사도 equals 규약과 똑같이 반사성, 대칭성, 추이성을 충족해야 한다는 뜻이다. 그렇기 때문에 equals()의 주의사항과 같이 상속을 사용해 새로운 값을 추가하면 규약을 지킬 방법이 없다.
( x.compareTo(y) == 0 ) == (x.equals(y)) 강제는 아니지만 지키자
- compareTo 메서드로 수행한 동치성 테스트의 결과가 equals와 같아야 한다.
- 이 정의를 지키지 않는다면 어떻게 될까??
- 이 클래스의 객체를 정렬된 컬렉션에 넣으면 해당 컬렉션이 구현한 인터페이스 (Collection, Set, Map)에 정의된 동작과 어긋날 수도 있다. 정렬된 컬렉션들은 동치성을 비교할 때 equals대신 compareTo를 사용하기 때문이다.


작성 요령
Comparable은 타입을 인수로 받는 제네릭 인터페이스이므로 compareTo() 메서드의 입력 인수의 타입을 확인할 필요가 없다.
compareTo() 메서드는 각 필드가 동치인지를 비교하는 게 아니라 그 순서를 비교한다.
객체 참조 필드를 비교하려면 compareTo() 메서드를 재귀적으로 호출한다.
Comparable을 구현하지 않은 필드나 표준이 아닌 순서로 비교해야 한다면 비교자(Comparator)를 대신 사용한다. 비교자는 직접 만들거나 자바가 제공하는 것 중에 골라 사용할 수 있다.
클래스에 핵심 필드가 여러 개라면 어느 것을 먼저 비교하느냐가 중요하다.
compare() vs compareTo()
compare() 메서드는 Comparator 인터페이스를 구현한 클래스에서 두 객체의 비교를 수행하며,
compareTo() 메서드는 Comparable 인터페이스를 구현한 클래스에서 객체의 순서를 결정하는 데 사용된다.
예시
Student 클래스를 만들어 compareTo를 구현해 보자.

전공학점, 교양학점, 총학점을 가지는 Student 클래스를 구성했다.
해당 클래스는 원래 Comparable이 구현되어 있지 않아 정렬이 불가능하지만 이제 구현하면 내가 원하는 순서대로 정렬할 수 있다.

내가 구현한 compareTo는 1순위로 전공학점을 오름차순으로, 2순위로 교양학점을 오름차순으로 정렬하였다.
1순위로 비교하는 result가 동일하다는 의미인 0이 나온다면 2순위로 다시 검사하고 아니라면 바로 결괏값을 return 해주자.

위의 compareTo를 비교자 생성 매서드와 팀을 꾸려 메서드 연쇄 방식으로 다음과 같이 간단하게 구현할 수 있지만 약간의 성능 저하가 뒤따른다. 이건 나중에 다시 알아보자..

students 배열을 내가 정의한 순서대로 정렬되었는지 확인해 보자.

1순위 전공학점, 2순위 교양학점으로 잘 정렬되었다.
이렇게 compareTo를 이용해 정렬할 수 있는데 만약 사용자가 클래스에서 구현된 우선순위가 아니라 내 마음대로 우선순위를 정하고 싶을 때는 어떡할까?
바로 위에서 말한 Comparable을 구현하지 않은 필드나 표준이 아닌 순서로 비교해야 한다면 비교자 (Comparator)를 사용하라. 이 말을 코드로 한번 쳐보자.

main 함수에서 Comparator를 사용해서 총학점을 기준으로 정렬하도록 했다.

총학점을 기준으로 오름차순 정렬이 잘 되었다.
주의할 점
공부하다가 되게 뜨끔한 내용이다.

이따금 이런식으로 값의 차를 이용해서 return 값이 0, 음수, 양수로 만들어 비교하는 compare를 구현하는 경우가 있는데, 이 방식은 정수 오버플로우나 부동소수점 계산 방식에 따른 오류를 낼 수 있다.
실제로 필자는 저 위의 방식으로 구현했다가 부동소수점 계산 방식 오류로 정렬이 잘못되어 몇 시간 동안 오류 찾느라 고생한 적이 있다.
이 방식 말고 오늘 배운 두가지 방식을 이용해 구현하자.
'Java > Effective Java' 카테고리의 다른 글
[Effective Java] Item 16. public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라 (2) | 2023.03.21 |
---|---|
[Effective Java] Item 15. 클래스와 멤버의 접근 권한을 최소화하라 (0) | 2023.03.21 |
[Effective Java] Item 13. clone 재정의는 주의해서 진행하라 (0) | 2023.03.20 |
[Effective Java] Item 12. toString을 항상 재정의하라 (0) | 2023.03.09 |
[Effective Java] Item 11. equals를 재정의 하려거든 hashCode도 재정의 하라 (2) | 2023.03.08 |