Effective Java - Item 14

Updated:

Effective Java

Item 14. Comparable을 구현할지 고려하라

Comparable

Comparable 인터페이스는 compareTo 메서드를 재정의 함으로써, 해당 객체들을 손쉽게 정렬 할 수 있다.
따라서 Comparable을 구현한 객체들의 배열은 Arrays.sort(arr)과 같이 정렬이 가능하다.
비교를 활용하는 클래스의 예로는 정렬된 컬렉션인 TreeSetTreeMap,
검색과 정렬 알고리즘을 활용하는 유틸리티 클래스인 CollectionsArrays가 있다.

public interface Comparable<T>{
  int compareTo(T t);
}

compareTo 메서드의 일반 규약

  • 이 객체가 주어진 객체보다 작으면 음의 정수를, 같으면 0을, 크면 양의 정수를 반환한다.
  • 이 객체와 비교할 수 없는 타입의 객체가 주어지면 ClassCastException을 던진다.
  • 대칭성 : 두 객체 참조의 순서를 바꾸어 비교해도 같은 결과가 나온다.
  • 추이성 : 첫 번째가 두 번째보다 크고, 두 번째가 세 번째보다 크면 첫번째는 세번째보다 크다.
  • 일관성 :크기가 같은 객체들끼리는 어떤 객체와 비교하더라도 항상 같아야 한다.
  • compareTo 메서드로 수행한 동치성 테스트의 결과가 equals와 같아야 한다.

주의 사항

  • compareTo 메서드에서 필드를 비교할 때는 Type.compare 메소드를 사용하라.
    관계 연산자인 < , > 을 사용하는 것은 오류를 유발하니 지양한다.
  • 필드를 어느 것을 먼저 비교하느냐가 중요하다.
    가장 핵심적인 필드부터 비교한다. 비교 결과가 0이 면, 그 다음으로 중요한 필드를 비교한다.
  • Comparable을 구현하지 않은 필드나 표준이 아닌 순서로 비교해야 한다면 비교자(Comparator)를 대신 사용한다.
    위 방식을 사용하면 여러 필드를 연쇄 방식으로 비교자를 생성해 비교할 수 있다.
    그리고 이 비교자를 Comparable 인터페이스가 원하는 compareTo 메서드를 구현하는데 활용할 수 있다.
    대신 약간의 성능이 저하된다.
private static final Comparator<SampleClass> COMPARATOR = 
    comparingInt((SampleClass sc)->sc.first)
      .thenComparingLong(sc.second)
        .thenComparingDouble(sc.third);

public int compareTo(SampleClass sc){
  return COMPARATOR.compare(this, sc);
}
  • 안티 패턴 : 값의 차를 기준으로 비교
    값의 차이를 기준으로 첫 번째 값이 두 번째 값보다 작으면 음수를, 같으면 0을 크면 양수를 반환하는 메서드를 만들면 안된다.
static Comparator<Object> hashCodeOrder = new Comparator<Object>() {
    @Override
    public int compare(Object o1, Object o2) {
        return o1.hashCode() - o2.hashCode();
    }
}

이 방식은 정수 오버플로를 일으키거나, 오류를 낼 수 있다.

정리

순서를 고려해야 하는 값 클래스를 작성한다면 꼭 Comparable 인터페이스를 구현한다.
이를 통해 인스턴스들을 쉽게 정렬하고, 검색하고, 비교 기능을 제공하는 컬렉션과 어우러 지도록 한다.
< 와 > 연산자는 쓰지 말아야 한다. 그 대신 박싱된 기본 타입 클래스가 제공하는 정적 compare 메서드나 Comparator 인터페이스가 제공하는 비교자 생성 메서드를 사용한다.

출처

Joshua Bloch. Effective Java 3/E. n.p.: 인사이트, 2018년 11월 1일.

참고 페이지