본문 바로가기
이펙티브 자바

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

by 아토로 2022. 3. 9.

Comparable 인터페이스

  • Comparable을 구현해다는 것은 그 클래스의 인스턴스들에는 자연적인 순서가 있음을 뜻한다.
  • Comparable을 구현한 객체들의 배열은 손쉽게 정렬할 수 있다. (Arrays.sort(a);)
  • 자바 플랫폼 라이브러리의 모든 값 클래스와 열거 타입(아이템 34)이 Comparable을 구현했다.
public interface Comparable<T> {
	int compareTo(T t);
}

compareTo 메서드의 일반 규약

  • 이 객체와 주어진 객체의 순서를 비교한다.
    • 이 객체가 주어진 객체보다 작으면 음의 정수를, 같으면 0을, 크면 양의 정수를 반환한다.
    • 이 객체와 비교할 수 없는 타입의 객체가 주어지면 ClassCastException을 던진다.
  • sgn(부호 함수): 표현식의 값이 음수, 0 양수일 때 -1, 0, 1을 반환한다.
  • sgn(x.compareTo(y)) == -sgn(y.compareTo(x))
  • (x.compareTo(y) > 0 && y.compareTo(z) > 0)이면 x.compareTo(z) > 0이다. (추이성)
  • x.compareTo(y) == 0이면 sgn(x.compareTo(z)) == sgn(y.compareTo(z))다.
  • (x.compareTo(y) == 0) == (x.equals(y)), 필수는 아니다.
    • 이 규약을 지키지 않으면, Collection, Set, Map은 동치성을 비교할 때 equals 대신 compareTo를 사용하기 때문에 의도치 않은 결과를 낼 수 있다.

객체 참조 필드가 하나뿐인 비교자

public final class CaseInsensitiveString implements Comparable<CaseInsensitiveString> {
	public int compareTo(CaseInsensitiveString cis) {
		return String.CASE_INSENSITIVE_ORDER.compare(s, cis.s);
	}
}

기본 타입 필드가 여럿일 때의 비교자

약간의 성능 저하가 발생한다.

public int compareTo(PhoneNumber pn) {
	int result = Short.compare(areaCode, pn.areaCode);  // 가장 중요한 필드
	if (result == 0) {
		result = Short.compare(prefix, pn.prifix);  // 두번째로 중요한 필드
		if (result == 0) {
			result = Short.compare(lineNum, pn.lineNum);  // 세번째로 중요한 필드
		}
	}
	return result;
}

비교자 생성 메서드를 활용한 비교자

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);
}

사용하면 안 되는 방식

  • compareTo 메서드에서 관계 연산자 <와 >를 사용하는 이전 방식은 거추장스럽고 오류를 유발하니, 이제는 추천하지 않는다.
  • 이따금 ‘값의 차'를 기준으로 첫 번째 값이 두번째 값보다 작으면 음수를, 두 값이 같으면 0을, 첫번째 값이 크면 양수를 반환하는 방식은 사용하면 안 된다. 이 방식은 정수 오버플로를 일으키거나 IEEE 754 부동소수점 계산 방식에 따른 오류를 낼 수 있다.

핵심 정리

  • 순서를 고려해야 하는 값 클래스를 작성한다면 꼭 Comparable 인터페이스를 구현해야 한다.
  • compareTo 메서드에서 필드의 값을 비교할 때 <와 > 연산자는 쓰지 말자.
  • 박싱된 기본 타입 클래스가 제공하는 정적 compare 메서드나 Comparator 인터페이스가 제공하는 비교자 생성 메서드를 사용하자.

댓글