규칙12. Comparable 구현을 고려하라.

compareTo 메서드는 Comparable 인터페이스에 포함된 유일한 메서드이다. Object의 equals 메서드와 비슷하지만 단순한 동치성 검사 이외에 순서 비교가 가능하며 좀더 일반적이다.
compareTo 메서드의 일반 규약은 equals와 비슷하다.

규약

규약 객체와 인자로 주어진 객체를 비교한다. 이 객체의 값이 인자로 주어진 객체보다 작으면 음수, 같으면 0, 크면 양수를 반환한다. 인자로 전달된 객체의 자료형이 비교불가능한 자료형일 땐 ClassCastException예외를 던진다.

규약1. compareTo를 구현할 때는 모든 x와 y에 대해 sgn(x.compareTo(y))== -sgn(y.compareTo(x))가 만족되도록 해야 한다. (y.compareTo(x)가 예외를 발생시킨다면 x.compareTo(y)도 그래야하고, 그 역도 성립해야 한다.
규약2. compareTo를 구현할 때는 추이성이 만족되도록 해야한다. (x.compareTo(y)>0 && y.compareTo(z) >0 ) 이면 x.compareTo(z)>0 이어야 한다.
규약3. 마지막으로 x.compareTo(y) == 0 이면 sgn(x.compareTo(z)) == sgn(y.compareTo(z))의 관계가 모든z에 대해 성립하도록 해야 한다.
sgn함수는 음수일때 -1, 0일때 0, 양수일때 1을 반환

또한 강력히 추천되지만 절대적으로 요구되는것이 아닌 조건으로 (x.compareTo(y) ==0) == (x.equals(y))이다. 일반적으로 Compareble 인터페이스를 구현하면서 이 조건을 만족하지 않는 클래스는 반드시 그 사실을 명시해야 한다.

equals와 다르게 compareTo는 비교대상이 서로다를 경우 ClassCastException을 던지는것이 허용되어있다. 그리고 통상적으로 반드시 이렇게 동작해야 한다. 규약에서 이를 강제하지는 않지만 1.6 에 속한 어떤 클래스도 이런 비교를 하지 않는다!

compareTo 규약을 준수하지 않는 클래스는 TreeSet, TreeMap, Arrays, Collection 같은 비교연산에 기반한 클래스들을 오작동 시킬 수 있다.

즉 compareTo 또한 반사성, 대칭성, 추이성을 만족해야 한다. 따라서 compareTo 규약을 만족하면서 클래스를 계승하여 새로운 값 컴포넌트를 추가할 방법은 없다.

Compareble 인터페이스를 구현하는 클래스에 값 요소를 추가하고 싶을 때는 원래 클래스를 계승하여 확장하는 대신, 원래 클래스 객체를 필드로 포함하는 새로운 클래스를 만들고, 원래 클래스의 객체를 반환하는 뷰메서드를 추가한다, 이렇게 하면 원하는대로 compareTo 메서드는 원하는 대로 정의할 수 있다.

Comparable 인터페이스

Comparable 인터페이스는 자료형을 인자로 받는 제네릭 인터페이스이므로 compareTo 메서드의 인자 자료형은 컴파일 시간에 정적으로 결정된다. 따라서 인자로 받은 객체의 자료형을 검사하거나 형 변환할 필요가 없다. 잘못된 자료형 객체를 인자로 넘길경우 컴파일이 불가능하기 호출이 불가능하다. 또한 null이 인자로들어왔을 때는 NullPointerException 예외를 발생시켜야 한다.
또한 객체참조 필드는 compareTo 메서드를 재귀적으로 호출하여 비교한다. 비교할 필드가 Comparable을 구현하지 않고 있거나 좀 특이한 순서 관계를 사용해야 할 경우에는 Comparator를 명시적으로 사용할 수 있다. Comparetor는 직접 작성할 수도 있고 아래의 compareTo 메서드에서처럼 이미 있는 Comparator를 사용할 수도 있다(String.CASE_INSENSITIVE_ORDER.compare(…..)같이).

비교

클래스에 선언된 중요 필드가 여러개인 경우 비교 순서가 중요하다. 가장 중요한 필드부터 차례로 비교해야 한다.
compareTo 메서드의 일반 규약이 반환값의 부호면 명시하고 그 크기에 대해서는 언급하고 있지않다. 이점을 이용해

default
1
2
3
4
public int compare(PhoneNumber pn){
...
return a-b;
}

와같이 정의하는게 코드를 단순화하고 보다 빠르게 동작하도록 할 수 있다.
하지만 i가 큰 int값이고 j가 큰 음수 int 값일 때 (i-j)는 오버플로 되어 음수가 되어버리므로 부적절한 결과가 나온다.치명적 시스템 문제를 발생시키도 있으므로 주의하자.

Share