들어가며
compareTo는 단순 동치성 비교에 더해 순서까지 비교할 수 있으며 제네릭하다. Comparable을 구현했다는 것은 그 클래스의 인스턴스들에는 자연적인 순서가 있음을 뜻한다. 그래서 Comparable을 구현한 객체들의 배열은 손쉽게 정렬이 가능하다.
1 | Arrays.sort(someArray); |
compareTo 메소드 일반 규약
- 이 객체와 주어진 객체의 순서를 비교한다. 이 객체가 주어진 객체보다 작다면 음의 정수를, 크다면 양의 정수를, 같다면 0을 반환한다. 비교할 수 없는 타입이라면 ClassCastExceptiond을 던진다.
- Comparable을 구현한 클래스는 모든 x, y에 대해 sgn(x.compareTo(y)) == -sgn(y.compareTo(x))여야 한다.
- Comparable을 구현한 클래스는 추이성을 보장해야 한다.
- Comparable을 구현한 클래스는 모든 z에 대해 x.compareTo(y) == 0이면 sng(x.compareTo(z)) == sgn(y.compareTo(z))다.
- (x.compareTo(y) == 0) == (x.equals(y)여야 한다. 이는 필수는 아니지만 지키는게 좋다. 이 권고를 지키지 않는다면 그 사실을 지키지 아니한다고 명시하는게 좋다.
이 규약들은 compareTo 메소드로 수행하는 동치성 검사는 반사성, 대칭성, 추이성을 충족해야 함을 의미한다. 또한 마지막 규약은 지키는것을 권고한다. 이를 따르지 않는 대표적인 클래스인 BigDecimal을 예로 들어보자. new BigDecimal(“1.0”)과 new BigDecimal(“1.00”)은 equals 메소드로 하면 다르기 때문에 HashSet은 두 개의 원소를 가지게 된다. 하지만 compareTo로 하면 같기 때문에 TreeSet에서는 원소를 하나만 가지게 된다.
compareTo 작성 요령
Comparable은 타입을 인수로 받는 제네릭 인터페이스이므로 compareTo 메소드의 인수 타입은 컴파일 타임에 정해진다. 또한 null을 인수로 넣어 호출하면 NullPointerException을 던져야 한다.
클래스에 핵심 필드가 여러 개라면 어느 것을 먼저 비교하느냐가 중요해진다. 핵심적인 필드부터 비교해 나가는 것이 좋다.
또한 비교자 생성 메소드를 통해 비교자를 만들 수 있다. 간결하다는 장점이 있지만 10%의 성능 저하가 있다.
1 | private static final Comparator<PhoneNumber> COMPARATOR = |
위 코드를 보면 최초 비교시 람다식에서 타입 추론을 할 수 있도록 만들어 두었다. 그 이후부터는 타입 추론을 이요하여 인스턴스 변수를 사용한다.
다음과 같은 메소드는 정의하면 안된다.
1 | static Comparator<Object> hashCodeOrder = new Comparator<>() { |
위 코드는 오버플로나 IEEE 754 부동소수점 계산 방식에 따른 오류를 범할 수 있다. 그렇다고 딱히 크게 빠르지도 않다. 그렇기 때문에 다음과 같은 방식을 사용하자.
1 | static Comparator<Object> hashCodeOrder = new Comparator<>() { |