ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Comparable을 구현할지 고려하라
    JAVA/Effective java 2021. 1. 21. 17:54

    Comparable 인터페이스의 유일무이한 메서드인 compareTo를 알아보자.

     

     

    • compareTo는 Object의 메서드가 아니다. 
    • compareTo는 Object의 equals와 비슷하지만 두 가지가 다르다.
    • 단순 동치성 비교에 더해 순서까지 비교할 수 있으며 제네릭하다. 

     

    Arrays.sort(a);
    • Comparable을 구현했다는 것은 그 클래스의 인스턴스들에는 자연적인 순서가 있음을 뜻하고
    • 따라서 Comparable을 구현한 객체들의 배열은 다음처럼 손쉽게 정렬할 수 있는 것이다.

     

    • 검색, 극단값 계산, 자동 정렬 컬렉션 관리 역시 쉽게 할 수 있다.
    • 다음 프로그램은 명령줄 인수들을 알파벳순으로 출력한다. String이 Comparable을 구현한 덕이다.
    • 사실상 자바 플랫폼 라이브러리의 모든 값 클래스와 열거 타입이 Comparable을 구현했다. 
    • 알파벳, 숫자, 연대 같이 순서가 명확한 값 클래스를 작성한다면 반드시 Comparable 인터페이스를 구현하자.

     

     

    Comparable 인터페이스에 compareTo

     

    compareTo 메서드의 일반 규약은 equals의 규약과 비슷하다.

    이 객체와 주어진 객체의 순서를 비교한다. 이 객체가 주어진 객체보다 작으면 음의 정수를, 같으면 0을, 크면 양의 정수를 반환한다. 이 객체와 비교할 수 없는 타입의 객체가 주어지면 ClassCastException을 던진다.

    다음 설명에서 sgn(표현식) 표기는 수학에서 말하는 부호 함수(signum function) 을 뜻하며, 표현식의 값이 음수,0,양수일 때 -1,0,1을 반환하도록 정의했다.

    Comparable을 구현한 클래스는 모든 x,y에 대해 sgn(x.compareTo(y)) == -sgn(y.compareTo(x))여야 한다. 따라서 x.compareTo(y)는 y.comopareTo(x)가 예외를 던질때에 한해 예외를 던져야 한다.

    Comparable을 구현한 클래스는 추이성을 보장해야 한다. 즉, (x.compareTo(y) > 0 && y.compareTo(z) > 0) 이면 x.compareTo(z) > 0 이다.

    Comparable을 구현한 클래스는 모든 z에 대해 x.compareTo(y) == 0 이면 sgn(x.compareTo(z)) == sgn(y.compareTo(z)) 이다.

    이번 권고가 필수는 아니지만 꼭 지키는 게 좋다. (x.compareTo(y) == 0) == (x.equals(y))여야 한다. Comparable을 구현하고 이 권고를 지키지 않는 모든 클래스는 그 사실을 명시해야 한다. 다음과 같이 명시하면 적당할 것이다.

    "주의: 이 클래스의 순서는 equals메서드와 일관되지 않다"

     

    • 모든 객체애 대해 전역 동치관계를 부여하는 equals 메서드와 달리, compareTo는 타입이 다른 객체를 신경 쓰지 않아도 된다. 
    • 타입이 다른 객체가 주어지면 간단히 ClassCastException을 던져도 되며, 대부분 그렇게 한다.
    • hashCode 규약을 지키지 못하면 해시를 사용하는 클래스와 어울리지 못하듯, compareTo 규약을 지키지 못하면 비교를 활용하는 클래스와 어울리지 못한다.
    • 비교를 활용하는 클래스의 예로 정렬된 컬렉션인 TreeSet와 TreeMap, 검색과 정렬 알고리즘을 활용하는 유틸 클래스인 Collections와 Arrays가 있다..

     

     

    • compareTo와 equals가 일관되지 않는 BigDecimal 클래스를 예로 생각해보자.
    • 빈 HashSet 인스턴스를 생성한 다음 new BigDecimal("1.0")과 new BigDecimal("1.00")을 차례로 추가한다.
    • 이 두 BigDecimal은 equals 메서드로 비교하면 서로 다르기 때문에 HashSet은 원소를 2개 갖게 된다.
    • 하지만 HashSet 대신 TreeSet을 사용하면 원소를 하나만 갖게 된다. 
    • compareTo 메서드로 비교하면 두 BigDecimal 인스턴스가 똑같이 때문이다.

     

    • Comparable은 타입을 인수로 받는 제네릭 인터페이스이므로 compareTo 메서드의 인수 타입은 컴파일 타임에 정해진다.
    • 입력 인수의 타입을 확인하거나 형변환할 필요가 없다.

     

     

     

     

    • 클래스에 핵심 필드가 여러 개라면 어느 것을 먼저 비교하느냐가 중요해진다.
    • 가장 핵심적인 필드부터 비교해나가자.
    • 비교 결과가 0이 아니라면, 즉 순서가 결정되면 거기서 끝이다. 그 결과를 곧장 반환하자.
    • 핵심  필드가 똑같다면 같지 않은 필드를 찾을 때까지 그다음 중요한 필드를 비교해나간다.

     

     

    • 자바 8에서는 Comparator 인터페이스가 일련의 비교자 생성 메서드와 팀을 꾸려 메서드 연쇄 방식으로 비교자를 생성할 수 있게 되었다.
    • 이 비교자들을 Comparable 인터페이스가 원하는 compareTo 메서드를 구현하는데 활용할 수 있다.
    • 이 방식들은 간결하지만 약간의 성능 저하가 뒤따른다.

     

     

    핵심 정리


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

     

    참고 자료 


    이펙티브 자바

Designed by Tistory.