-
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 인터페이스를 구현하자.
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 인터페이스가 제공하는 비교자 생성 메서드를 사용하자.
참고 자료
'JAVA > Effective java' 카테고리의 다른 글
public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라 (0) 2021.02.01 클래스와 멤버의 접근 권한을 최소화하라 (0) 2021.01.23 clone 재정의는 주의해서 진행하라 (0) 2021.01.20 toString을 항상 재정의하라 (0) 2021.01.19 equals를 재정의하려거든 hashCode도 재정의하라 (0) 2021.01.14