[Effective Java] 아이템14 - Comparable을 구현할지 고려하라
Comparable 인터페이스는 Object의 메서드가 아니지만 성격이 Object의 equals 와 동일해서 해당 챕터에서 다룬다.
compareTo
는 단순 동치성 비교에 더해 순서까지 비교하며 Generic하다.
Comparable을 구현했다는 것은 그 클래스의 인스턴스들에는 자연적인 순서가 있음을 뜻한다. 그래서 Comparable을 구현한 객체들의 배열은 다음처럼 손쉽게 정렬할 수 있다.
Arrays.sort(a);
순서를 고려해야 하는 값 클래스를 작성한다면 꼭 Comparable 인터페이스를 구현해서 그 인스턴스들을 쉽게 정렬하고, 검색하고, 비교 기능을 제공하는 컬렉션과 어우러지도록 해야한다.
Comparable의 compareTo
재정의
public interface Comparable<T> {
/**
* Compares this object with the specified object for order. Returns a
* negative integer, zero, or a positive integer as this object is less
* than, equal to, or greater than the specified object.
*
*/
public int compareTo(T o);
}
객체가 주어진 객체보다 작으면 음의 정수를, 같으면 0을, 크면 양의 정수를 반환한다.
equlas
메서드는 모든 객체에 대해 전역 동치관계를 체크했어야 했는데, compareTo
는 타입이 다른 객체를 신경쓰지 않아도 된다. 타입이 다르면 ClassCastException
을 던진다.
equals
메서드 처럼, 기존 클래스를 확장한 구체 클래스(하위 클래스) 에서 새로운 값 컴포넌트를 추가했다면 compareTo
규약을 지킬 방법이 없다. 따라서 뷰 메서드를 제공하자 (자세한건 item10을 다시보자)
compareTo
주의해야할 점
compareTo 메서드로 수행한 동치성 테스트의 결과가 equals와 같아야 한다.
BigDecimal 클래스는 compareTo와 equals 가 일관되지 않다.
new BigDecimal("1.0")
, new BigDecimal("1.00")
을 HashSet에 차례로 추가한다.
이 두 클래스는 equals
메서드로는 false를 반환해서 HashSet은 원소를 2개 가지게 되지만 TreeSet을 사용하면 compareTo로 비교하기 때문에 한개의 원소만 가지고 있다. compareTo로 비교하면 동일하기 때문이다.
compareTo
구현법
compareTo 메서드는 각 필드가 동치인지를 비교하는게 아니라 그 순서를 비교한다.
Comparable<T>
인터페이스를 implement 하면 된다.
public final class CaseInsensitiveString
implements Comparable<CaseInsensitiveString> {
private final String s;
// 자바가 제공하는 비교자를 사용해 클래스를 비교한다.
public int compareTo(CaseInsensitiveString cis) {
return String.CASE_INSENSITIVE_ORDER.compare(s, cis.s);
}
}
자바7 부터는 박싱된 기본타입클래스에도 그냥 비교자가 아니라 comapre
메서드를 사용하자
가장 핵심적인 필드부터 비교해나가자
가장 순서가 다를 것 같은 핵심 필드부터 비교하면 성능이 더 빠를 것 이다.
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;
}
이제 위 코드를 보면 ,, chaning 으로 바꾸고 싶은 욕구가 생긴다.
Comparator 인터페이스
자바8에서는 Comparator
인터페이스가 메서드 연쇄방식으로 비교자를 생성할 수 있게 되었다. 간결해보이지만, 약간의 (10% 정도) 성능 저하가 있긴 하다.
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);
}
Comparator
값을 위한 comparing 메소드 뿐만 아니라 객체 참조용 비교자 생성 메서드도 있다. comparing
이라는 메서드로 키와, 비교할 비교자 까지 파라미터로 받을 수 있다.
객체를 비교할 때는 hashCode를 사용해서 비교하는 것도 방법이다.
Integer.compare(o1.hashCode(), o2.hashCode());
Item MSA value 값 비교하는거에,, hashCode를 사용해보면 어떨까? 좀 위험하려나
댓글남기기