[Effective Java] 아이템14 - Comparable을 구현할지 고려하라

Comparable 인터페이스는 Object의 메서드가 아니지만 성격이 Object의 equals 와 동일해서 해당 챕터에서 다룬다.

compareTo는 단순 동치성 비교에 더해 순서까지 비교하며 Generic하다.

Comparable을 구현했다는 것은 그 클래스의 인스턴스들에는 자연적인 순서가 있음을 뜻한다. 그래서 Comparable을 구현한 객체들의 배열은 다음처럼 손쉽게 정렬할 수 있다.

Arrays.sort(a);

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

Comparable의 compareTo 재정의

public interface Comparable<T> {
    /**
     * Compares this object with the specified object for order.  Returns a
     * negative integer, zero, or a positive integer as this object is less
     * than, equal to, or greater than the specified object.
     *
     */
    public int compareTo(T o);
}

객체가 주어진 객체보다 작으면 음의 정수를, 같으면 0을, 크면 양의 정수를 반환한다.

equlas 메서드는 모든 객체에 대해 전역 동치관계를 체크했어야 했는데, compareTo는 타입이 다른 객체를 신경쓰지 않아도 된다. 타입이 다르면 ClassCastException을 던진다.

equals 메서드 처럼, 기존 클래스를 확장한 구체 클래스(하위 클래스) 에서 새로운 값 컴포넌트를 추가했다면 compareTo 규약을 지킬 방법이 없다. 따라서 뷰 메서드를 제공하자 (자세한건 item10을 다시보자)

compareTo 주의해야할 점

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

BigDecimal 클래스는 compareTo와 equals 가 일관되지 않다.

new BigDecimal("1.0"), new BigDecimal("1.00")을 HashSet에 차례로 추가한다.

이 두 클래스는 equals 메서드로는 false를 반환해서 HashSet은 원소를 2개 가지게 되지만 TreeSet을 사용하면 compareTo로 비교하기 때문에 한개의 원소만 가지고 있다. compareTo로 비교하면 동일하기 때문이다.

compareTo 구현법

compareTo 메서드는 각 필드가 동치인지를 비교하는게 아니라 그 순서를 비교한다.

Comparable<T> 인터페이스를 implement 하면 된다.

public final class CaseInsensitiveString
        implements Comparable<CaseInsensitiveString> {
    private final String s;

    // 자바가 제공하는 비교자를 사용해 클래스를 비교한다.
    public int compareTo(CaseInsensitiveString cis) {
        return String.CASE_INSENSITIVE_ORDER.compare(s, cis.s);
    }

}

자바7 부터는 박싱된 기본타입클래스에도 그냥 비교자가 아니라 comapre 메서드를 사용하자

가장 핵심적인 필드부터 비교해나가자

가장 순서가 다를 것 같은 핵심 필드부터 비교하면 성능이 더 빠를 것 이다.

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

이제 위 코드를 보면 ,, chaning 으로 바꾸고 싶은 욕구가 생긴다.

Comparator 인터페이스

자바8에서는 Comparator 인터페이스가 메서드 연쇄방식으로 비교자를 생성할 수 있게 되었다. 간결해보이지만, 약간의 (10% 정도) 성능 저하가 있긴 하다.

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

Comparator 로 compare를 메서드 체이닝으로 만들고, 해당 공식을 final 로 설정해, 캐시로 활용할 수 있게 만들었다.

값을 위한 comparing 메소드 뿐만 아니라 객체 참조용 비교자 생성 메서드도 있다. comparing 이라는 메서드로 키와, 비교할 비교자 까지 파라미터로 받을 수 있다.

객체를 비교할 때는 hashCode를 사용해서 비교하는 것도 방법이다.

Integer.compare(o1.hashCode(), o2.hashCode());

Item MSA value 값 비교하는거에,, hashCode를 사용해보면 어떨까? 좀 위험하려나

댓글남기기