본문 바로가기

공부 기록/Java

[이펙티브 자바 3/E] 아이템14 - Comparable을 구현할지 고려하라

- Comparable을 구현했다는 것은 그 클래스의 인스턴스들에는 자연적인 순서(natural order)가 있음을 뜻한다. 그래서 객체들의 배열을 Arrays.sort(a);와 같이 손쉽게 정렬할 수 있다.

- 알파벳, 숫자, 연대 같이 순서가 명확한 값 클래스를 작성한다면 반드시 Comparable 인터페이스를 구현하자.

- compareTo 메서드의 일반 규약은 equals의 규약(아이템 10)과 비슷하다. 

- compareTo 메서드로 수행한 동치성 테스트의 결과는 equals와 같아야 한다.


- compareTo 메서드에서 관계 연산자 <와 >를 사용하는 이전 방식은 거추장스럽고 오류를 유발하니, 이제는 추천하지 않는다. => 박싱된 기본 타입 클래스들에 새로 추가된 정적 메서드인 compare를 이용하면 된다.

- 클래스에 핵심 필드가 여러 개라면 어느 것을 먼저 비교하느냐가 중요해진다. 가장 핵심적인 필드부터 비교해나가자.

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;
}

- 이 방식은 간결하지만 약간의 성능 저하가 뒤따른다.


- 자바의 정적 임포트 기능을 이용하면 정적 비교자 생성 메서드들을 그 이름만으로 사용할 수 있어 코드가 훨씬 깔끔해진다.

private static final Comparator<PhoneNumber> COMPARATOR =
        comparingInt(PhoneNumber pn) -> pn.areaCode)
            .thenComparingInt(pn -> pn.prefix)
            .thenComparingInt(pn -> pn.lineNum);
            
public int compareTo(PhoneNumber pn) {
    return COMPARATOR.compare(this, pn);
}

- 해시코드 값의 차를 기준으로 하는 아래의 방식은 사용하면 안 된다. 정수 오버플로를 일으키거나 부동소수점 계산 방식에 따른 오류를 낼 수 있다. 속도 또한 월등히 빠르지도 않을 것이다.

// 사용하면 안 된다!!!
static Comparator<Object> hashCodeOrder = new Comparator<>() {
    public int compare(Object o1, Object o2) {
        return o1.hashCode() - o2.hashCode();
    }
};

- 아래의 두 방식 중 하나를 사용하자.

// 정적 compare 메서드를 활용한 비교자
static Comparator<Object> hashCodeOrder = new Comparator<>() {
    public int compare(Object o1, Object o2) {
        return Integer.compare(o1.hashCode(), o2.hashCode());
    }
};
// 비교자 생성 메서드를 활용한 비교자
static Comparator<Object> hashCodeOrder = Comparator.comparingInt(o -> o.hashCode());

[핵심 정리]

순서를 고려해야 하는 값 클래스를 작성한다면 꼭 Comparable 인터페이스를 구현하여, 그 인스턴스들을 쉽게 정렬하고, 검색하고, 비교 기능을 제공하는 컬렉션과 어우러지도록 해야 한다.