14.Comparable을 구현할지 고려하라

신명철·2022년 2월 14일
0

Effective Java

목록 보기
12/80

compareTo

compareTo는 단순 동치성 비교에 순서까지 비교할 수 있고 제네릭하다. Comparable을 구현했다는 건 클래스의 인스턴스들에게 순서가 있음을 뜻한다.

compareTo 규약

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

  1. Comparable을 구현한 클래스는 모든 x,y 에 대해 sgn(x.compareTo(y))==-sgn(y.compareTo(x)) 여야 한다.
  2. Comparable을 구현한 클래스는 추이성을 보장해야 한다.
  3. Comparable을 구현한 클래스는 모든 z 에 대해서 x.compareTo(y) == 0 이면 sgn(x.compareTo(z)) == sgn(y.compareTo(z))다.
  4. (x.compareTo(y)==0)==(x.equals(y))여야한다.
    -> 필수는 아니지만 지키는게 좋다.

마지막 사항은 지키는것을 권고한다. 이를 따르지 않는 대표적은 클래스인 BigDecimal 을 예로 들어보자.

new BigDecimal("1.0")과 new BigDecimal("1.00")는 equals 메서드로 하면 다르기 때문에 HashSet은 두 개의 원소를 갖는다.
하지만, TreeSet은 compareTo를 사용하기 때문에 하나의 원소만 갖게 된다.

compareTo 작성 방법

Comparable은 타입을 인수로 받는 제너릭 인터페이스다. 그렇기 때문에 인수 타입은 컴파일 타임에 정해지고 null 을 넣으면 NPE 을 던져야한다.

기본 타입 필드가 여러 개 일때 비교자

만약, 클래스에 핵심 필드가 여러 개라면 필드의 비교 순서를 정하는 것이 중요하다.
-> 핵심적인 필드부터 비교해가는게 중요

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); // 세번째로 중요한 필드
        }
    }
}

필드의 정렬 우선순위를 정해 같은 값이 있을 때마다 조건을 추가한다.

private static final Comparator<PhoneNumber> COMPARATOR = 
	comparingInt((PhoneNumber pn) -> pn.areaCode)
		.thenComparingInt(pn -> pn.prefix)
		.thenComparingInt(ph -> pn.lineNum);
		
public int compareTo(PhoneNumber pn) {
	return COMPARATOR.compare(this, pn);
}
  • comparingInt 메서드를 import 해서 사용하고, 2번째 조건부터 thenComparingInt 를 사용해 비교자를 추가할 수 있다.

주의

public int compareTo (int x, int y) {
  return x < y ? 1 : (x == y) ? 0 : -1;
}
  • compareTo 메서드에서 관계 연산자를 사용하지 말아야 한다.(< 와 >)
  • 대신 Type.compare(T t1, T t2) 를 사용하여 비교하는 것이 좋다.
static Comparator<Object> hashCodeOrder = new Comparator<>() {
  (Object o1, Object o2) -> o1.hashCode() - o2.hashCode();
}
  • 위와 같이 값의 차를 이용한 방법을 사용하는 것도 정수 오버플로를 일으키거나 IEEE754 부동 소수점 계산 방식에 따른 오류를 일으킬 수도 있다.

아래와 같이 고쳐서 사용하는 것이 좋다

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());
profile
내 머릿속 지우개

0개의 댓글