#12 Comparable 인터페이스의 구현을 고려하자
모든 객체에 공통적인 메서드라는 주제를 다루고 있지만, 아래에 보이듯이 compareTo 메서드는 Object 클래스에 기본적으로 정의되어 있지 않습니다.
compareTo 메서드는 Comparable 인터페이스에 유일하게 존재하는 메서드입니다.
이번에는 Comparable 인터페이스의 compareTo 메서드에 대해 알아보겠습니다.
compareTo 메서드는 두 객체가 동일한지를 비교한다는 점에서 Object 클래스의 equals 메서드와 비슷합니다.
그리고 더 많은 기능을 지원하고 있습니다.
순서를 비교할 수 있음
제네릭 타입을 지원
객체들이 저장된 배열의 정렬을 간단히 할 수 있음 [ex Arrays.sort(a)]
객체들이 저장된 컬랙션에서 최대/최소 값을 찾고, 자동으로 정렬된 상태로 유지
아래 보듯이, compareTo 메서드가 구현된 String 클래스는 컬렉션에서 자동으로 정렬됩니다.
이처럼 Comparable 인터페이스를 구현하면, 이 인터페이스에 의존하는 자바의 알고리즘이나 컬렉션 클래스들과 자동으로 연동이 가능합니다.
따라서 대부분의 자바 라이브러리의 클래스들은 Comparable 인터페이스를 구현하고 있습니다.
만약 직접 만든 클래스가 알파벳/숫자/날짜와 같이 순서를 갖는다면, 반드시 Comparable 인터페이스를 구현해야 합니다.
compareTo 메서드는 순서를 판단하기 위해서 현재 객체와 지정 객체를 비교하는데, 만약 현재 객체의 값이 더 크면 양수, 작으면 음수, 같으면 0을 반환합니다.
x.compareTo(y) == -y.compareTo(x)
첫 번째 객체(x)가 두 번째 객체(y) 보다 크면, 반대로 두 번째 객체(y)가 첫 번째 객체(x) 보다 작습니다.
(x.compareTo(y) > 0 && y.compareTo(z) > 0)이면, x.compareTo(z) > 0입니다.
첫 번째 객체(x)가 두 번째 객체(y) 보다 크고 두 번째 객체(y)가 세 번째 객체(z) 보다 크면, 첫 번째 객체(x)가 세 번째 객체(z) 보다 큽니다.
x.compareTo(y) == 0이면, x.compareTo(z) == y.compareTo(z)입니다.
compareTo 비교에서 동일한 객체를 각각 다른 객체와 비교하면 같은 결과가 나와야 합니다.
(x.compareTo(y) == 0) == (x.equals(y)) [필수 아님]
compareTo 메서드의 동일 여부 결과와 equals 메서드 동일 여부 결과와 동일해야 합니다.
컬렉션 인터페이스의 보편적 계약은 equals 메서드의 보편적 계약을 따르지만, 객체를 정렬 상태로 유지하는 정렬 컬렉션은 compareTo 메서드의 보편적 계약을 따릅니다.
읽어보면 당연한 말입니다.
객체 간 순서를 보장하면 자연스레 계약들을 지키게 되어 있습니다.
내용이 equals 보편적 계약과 비슷한데, 마지막 부분은 생각할 필요가 있습니다.
아래 코드를 보면, 컬렉션(HashSet)과 정렬 컬렉션(TreeSet)을 이용하고 있습니다.
HashSet에서는 equals 메서드를 이용하고 있으므로 두 개의 BigDecimal 객체가 서로 다르다고 판정하고 있습니다.
하지만 TreeSet에서는 compareTo 메서드를 이용하고 있으므로 두 개의 BigDecimal이 같다고 판정하고 있습니다.
이처럼 순서를 보장하는 컬렉션을 이용할 때는 equals 메서드뿐만 아니라 compareTo 메서드도 구현해야 합니다.
객체 참조 필드는 compareTo 메서드를 재귀적으로 호출해서 비교합니다.
CaseInsensitiveString 클래스는 Comparable <CaseInsensitiveString> 형태로 Comparable 인터페이스를 구현하고 있습니다.
그리고 compareTo 메서드의 매개변수도 CaseInsensitiveString 타입입니다.
따라서 CaseInsensitiveString 타입의 객체 참조하고만 비교할 수 있습니다.
만약 CaseInsensitiveString 클래스가 Comparable 인터페이스를 구현하고 있지 않거나, 기존 순서와는 다르게 구현하고 싶다면 Comparator 인터페이스를 통해 구현할 수 있습니다.
기본형 필드는 compareTo 메서드를 재귀적으로 호출하지 않고, 아래와 같이 직접 값을 비교해야 합니다.
정수 타입의 기본형 필드 : '<' or '>' 연산자
부동 소수점 필드 : Double.compare or Float.compare
만약 비교할 필수 필드가 여러 개인 경우에는 우선순위에 따라 순서대로 필드를 비교해야 합니다.
compareTo 메서드는 반환되는 필드의 실제 값보다는 음수, 양수, 0 여부만 확인하면 됩니다.
여기서 주의할 점은 int가 표현할 수 있는 값의 범위가 한정(Integer.MAX_VALUE/MIN_VALUE)되어 있기 때문에 필드 값의 범위도 생각해야 합니다.
두 객체를 비교하는 로직은 은근히 작성해야 하는 경우가 있습니다.
컬렉션 내부에서 쓰이는 객체라면 좀 더 신경 써야겠습니다.