짧은 코멘트
- Comparable을 생각보다 종종 구현을 하게 된다.
- 안타깝게 항상 헷갈리는 부분이 있는데 결과가 음수 혹은 양수일때 어떤 것이 앞에 가느냐... 이다 이럴때 해당 객체에 대한 단위 테스트를 깔끔하게 작성해주면 정말 좋다 👍
Comparable을 구현할지 고려하라
compareTo는 Object equals와 유사하다. 다른 점이라곤,
- compareTo는 단순 동치성 비교에 더해 순서까지 비교할 수 있으며 제네릭 한 것과
- Comparable을 구현했다는 것은 그 클래스의 인스턴스들에는 자연적인 순서가 있음을 뜻하는 것이다.
Comparable을 구현하면 검색, 극단값 계산, 자동 정렬되는 컬렉션 관리도 쉽게 할 수 있다. 자바 플랫폼 라이브러리의 모든 값 클래스와 열거타입은 Comparable을 구현했다.
Comparable의 일반 규약은 equals와 비슷하다
A.compareTo(B)
는 A객체가 B객체보다 작으면 ‘음의 정수’ 같으면 ‘0’ 더 크면 ’양의 정수’를 반환해야 한다. 비교할 수 없다면 ClassCastException을 던진다.- sgn(x.compareTo(y)) = -sgn(y.compareTo(x))
- x.compareTo(y) > 0 이고 y.compareTo(z) > 0이면 x.compareTo(z) 이다.
- x.compareTo(y) == 0이면, sgn(x.compareTo(z)) == sgn(y.compareTo(z)) 이다.
- 이번 권고는 필수가 아니지만 꼭 지키는 것이 좋다. (x.compareTo(y) == 0) == (x.equals(y)) 만약 이 권고를 지키지 않는다면 그 사실을 명시해야 한다.
2번째부터 4번째까지 세 규약은 compareTo가 equals와 똑같이 반사성, 대칭성, 추이성을 충족해야 함을 뜻한다. 그래서 주의사항도 같다. 기존 클래스를 확장한 구체 클래스에서 새로운 값 컴포넌트를 추가했다면 compareTo 규약을 지킬 방법이 없다. 우회법도 같다. 상속대신 합성을 사용하면 된다.
compareTo의 마지막 규약은 필수가 아니지만 꼭 지키길 권한다.
(compareTo가 ’같다’라고 판단했으면 equals가 true여야 한다) > 정렬된 컬렉션들은 동치성을 비교할 때 equals 대신 compareTo를 사용하기 때문이다.
예를 들어 compareTo와 equals가 일관되지 않은 BigDecimal을 예로 들어 생각해보자. BigDecimal("1.0")
과 BigDecimal("1.00")
을 HashSet에 넣게 되면 HashSet은 equals를 사용하여 2개의 원소를 갖게 된다. 하지만 HashSet 대신 TreeSet을 사용하면 compareTo가 사용되어 1개의 원소를 갖게 된다.
compareTo를 구현할 때에는 관계 연산자인 > 와 < 를 사용하기 보다 박싱된 기본 타입 클래스들의 새로 추가된 정적 메소드를 사용하는 것이 좋다.
또한 성능을 높이기 위해서는 가장 핵심적인 필드부터 비교해 나가야 한다.
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;
}
Comparable을 구현하지 않은 필드나 표준이 아닌 순서로 비교해야 한다면 Comparator를 대신 사용하면 된다.
JAVA 8에서는 Comparator 인터페이스가 일련의 비교 생성 메소드(comparator construction method)와 팀을 꾸려 메소드 연쇄 방식으로 비교자 생성을 할 수 있게 되었다. 약간의 성능 저하가 있지만 훨씬 깔끔해진다.
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);
}
comparingInt는 람다를 인수로 받으며, 이 람다는 PhoneNumber에서 추출한 지역 코드를 기준으로 전화번호의 순서를 정하는 Comparator를 반환한다.
만약 객체의 hashCode를 기준으로 값을 비교하고 싶다면 o1.hashCode() - o2.hashCode()
라는 위험한 코드 대신 Integer.compare나 Comparator의 comparingInt를 사용하는 것이 좋다.
static Comparator<Object> hashCodeOrder = new Comparator<>() {
public int compare(Object o1, Object o2) {
return Integer.compare(o1.hashCode(), o2.hashCode());
}
}
static Comparator<Object> hashCodeOrder =
Comparator.comparingInt(o -> o.hashCode());
'개발 공부 기록하기 > 01. JAVA & Kotlin' 카테고리의 다른 글
짧은 코멘트와 함께하는 이펙티브 자바) #16 public 클래스에서는 public 필드가 아닌 접근자 메소드를 사용하라 (0) | 2020.10.14 |
---|---|
짧은 코멘트와 함께하는 이펙티브 자바) #15 클래스와 멤버의 접근 권한을 최소화하라 (0) | 2020.10.07 |
짧은 코멘트와 함께하는 이펙티브 자바) #13 clone 재정의는 주의해서 진행하라 (0) | 2020.10.05 |
짧은 코멘트와 함께하는 이펙티브 자바) #12 toString을 항상 재정의하라 (0) | 2020.10.01 |
짧은 코멘트와 함께하는 이펙티브 자바) #11 equals를 재정의하려거든 hashCode도 재정의하라 (0) | 2020.09.29 |