Comparable
을 구현햇다는 것은 그 클래스의 인스턴스들에는 자연적인 순서가 있다는 것을 뜻한다. 그래서 Comparable
을 구현한 객체들의 배열은 손쉽게 정렬이 가능하다.
Arrays.sort(a);
자바 플랫폼 라이브러리의 모든 값 클래스와 열거 타입이 Comparable을 구현했다.
알파벳, 숫자, 연대 같이 순서가 명확한 값 클래스를 작성한다면 반드시 Comparable 인터페이스를 구현하자.
compareTo 메서드의 규약은 equals의 규약과 비슷하다.
이 객체가 주어진 객체보다 작으면 음의 정수를, 같으면 0을, 크면 양의 정수를 반환한다. 이 객체와 비교할 수 없는 타입의 객체가 주어지면 ClassCastException을 던진다.
이는 반사성, 대칭성, 추이성을 충족해야 함을 뜻한다. (자세한거는 아이템10에서 equals 규약을 참조하자.)
그래서 기존 클래스를 확장한 구체 클래스에서 새로운 값 컴포넌트를 추가했다면 compareTo 규약을 지킬 방법이 없다.
그래서 우회법도 마찬가지로 Comparable을 구현한 클래스를 확장해 값 컴포넌트를 추가하고 싶다면, 확장하는 대신 독립된 클래스를 만들고, 이 클래스에 원래 클래스의 인스턴스를 가리키는 필드를 둔다. 그러고 내부 인스턴스를 반환하는 '뷰' 메서드를 제공하면 된다. 이렇게 하면 바깥 클래스에 우리가 원하는 compareTo 메서드를 구현할 수 있다.
ex> HashSet
인스턴스에 new BigDecimal("1.0")
과 new BigDecimal("1.00")
을 추가하면 두개로 인식된다. 그러나 TreeSet에서는 원소를 하나만 갖게된다.
public final class CaseInsensitiveString implements Comparable<CaseInsensitiveString> {
...
}
CaseInsensitiveString
이 Comparable<CaseInsensitiveString>
을 구현헀다. CaseInsensitiveString
끼리만 비교할 수 있다는 뜻으로, Comparable을 구현할 때 일반적인 패턴이다.
public int compareTo(Car o) {
if (this.position.isSmallerThan(o.position)) {
return -1;
} else if (this.position.equals(o.position)) {
return 0;
}
return 1;
}
Comparable 잘 구현해주셨어요.
여기서 조금 더 생각을 진전시켜볼까요?
Car가 비교 가능하다면 Position은 좀 더 당연하게 비교 가능한 객체가 아닐까 싶어요.
그래서 만약 Position 또한 Comparable의 구현체라고 한다면 그 내부는 Integer.compare() 이라는 안정된 API를 사용할 수 있을 거 같네요.
직접 compareTo를 구현하고 -1, 0, 1을 반환하는 건 가독성이나 실수의 가능성을 생각해봤을 때 피할 수 있으면 피하면 좋다고 생각합니다 :)
자바 7이전까지는 compareTo 메서드에서 정수 기본 타입 필드를 비교할 때는 관계 연산자인 <
와>
를, 실수 기본 타입을 비교할때는 Double.compare
와 Float.compare
를 사용하라고 권햇다.
자바7부터는 박싱된 기본 타입 클래스들에 새로 추가된 정적 메서드인 compare를 이용하면된다.
public int compareTo(Car o) {
return Integer.compare(this.position, o.getPosition());
}
public class Position implements Comparable<Position> {
private final int position;
...
@Override
public int compareTo(Position target) {
return position - target.position;
}
}
이렇게 하면 또 다른 문제점은 뭘까?
오버플로우나 언더플로우가 날 수도 있다는 점이다.
public int compareTo(PhoneNumber pn) {
int result = Short.compare(areaCode, pn.areaCode); // 가장 중요한 피륻
if (result == 0) {
result = Short.compare(prefix, pn.prefix);
if (result == 0) {
result = Short.compare(lineNum, pn.lineNum);
}
return result;
}
가장 핵심적인 필드부터 비교해나가서 결정되면 바로 반환하면 된다.
static Comparator<Object\> hashCodeOrder = new Comparator<>() {
public int compare(Object o1, Object o2) {
return Integer.compare()o1.hashCode(), o2.hashCode());
}
};
Comparator 인터페이스의 비교자 생성 메서드를 활용
private static final Comprator<Book> COMPARATOR = Comparator.comparingInt((Book book) -> book.getprice)
.thenComparaing(book -> book.title);
@Override
public int compareTo(Book o) {
return COMPARATOR.compare(this, o);
}