본문 바로가기
Java/Effective Java 3E

[이펙티브자바 3판] ITEM14. Comparable을 구현할지 고려하라

by 잭피 2020. 9. 14.

이번장의 핵심은...

순서를 고려해야 하는 값 클래스를 작성한다면 꼭 Comparable 인터페이스를 구현하여,

그 인터페이스들을 쉽게 정렬하고, 검색하고, 비교 기능을 제공하는 컬렉션과 어우러지도록 해야 한다

compareTo() 메서드에서 필드의 값을 비교할 때, "<"와 ">"연산자는 쓰지 말도록 하자

대신 박싱된 기본 타입 클래스가 제공하는 정적 compare() 메서드나

Comparator 인터페이스가 제공하는 비교자 생성 메서드를 사용하자


Comparable 인터페이스

compareTo()는 Object의 메서드가 아니다

성격은 2가지만 빼면 Object의 equals()와 같다

compareTo()는 단순 동치성 비교에 더해 순서까지 비교할 수 있다

Comparable을 구현했다는 것은 해당 클래스의 순서가 있음을 의미한다

따라서 Comparable을 구현한 객체들의 배열은 다음처럼 손쉽게 정렬할 수 있다

Arrays.sort(a);

CompareTo() 메서드의 일반 규약

이 객체와 주어진 객체의 순서를 비교한다

이 객체가 주어진 객체보다 작으면 음의 정수(-1), 같으면 0을, 크면 양의 정수(1)를 반환한다

이 객체와 비교할 수 없는 타입이 주어지면 ClassCaseException을 던진다

 

1. 대칭성

Comparable을 구현한 클래스는 모든 x, y에 대해서 sgn(x.compareTo(y)) == -sgn(y.compareTo(x))여야 한다

따라서 x.compareTo(y)가 예외를 던지면, y.compareTo(x)도 예외를 던져야 한다

 

2. 추이성

x.compareTo(y) > 0 && y.compareTo(z) > 0이면 x.compareTo(z) > 0이다

 

3. 반사성

x.compareTo(y) == 0이면 sgn(x.compareTo(z)) == sgn(y.compareTo(z))이다

 

4. equals

이는 필수는 아니지만 꼭 지키는 것이 좋다

(x.compareTo(y) == 0) == (x.equals(y))이다

이를 지키지 않는 모든 클래스는 그 사실을 명시해야 한다

주의 : 이 클래스의 순서는 equals 메서드와 일관되지 않다

 

CompareTo() 메서드의 구현 패턴

관계 연산자 "<", ">"를 사용하는 것은 거추장스럽고 오류를 유발하니 피해라

public int compareTo(int x, int y) {
	return (x < y) ? -1 : ((x == y) ? 0 : 1);
}

위처럼 사용하지 말고 아래처럼 사용하라

기본 타입인 경우 박싱된 기본타입 클래스의 compare() 함수, 또는 비교 클래스 자체의 compare()을 사용해라

public int compareTo (int x, int y) {
	return Integer.compare(x, y);
}

가장 핵심적인 필드부터 비교하자. 불필요한 연산을 줄여줄 것이다

public int compareTo(PhoneNumber pn) {
	int result = Short.compare(this.areaCode, pn.areaCode);
	if(result == 0) {
		result = Short.compare(this.prefix, pn.prefix);
		if(result == 0) {
			result = Short.compare(this.lineNum, pn.lineNum);
		}
	}
    return result;
}

자바 8에서 Comparator 인터페이스가 일련의 비교자 생성 메서드와 팀을 꾸려 연쇄 방식으로 비교자를 생성할 수 있게 되었다

그리고 이 비교자들을 Comparable 인터페이스가 원하는 compareTo 메서드를 구현하는 데 멋지게 활용할 수 있다

이 방식의 간결함에 매혹되지만, 약간의 성능 저하가 뒤따른다

 

PhoneNumber의 compareTo 메서드에 이 방식을 적용한 모습이다 

// 비교자 생성 메서드를 활용한 비교자
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);
}

값의 차를 기준으로 하는 비교자의 예

static Comparator<Object> hashCodeOrder = new Comparator<>() {
	public int compare(Object o1, Object o2) {
    	return o1.hashCode() - o2.hashCode();
    }
}

위 방식은 사용하면 안 된다

정수 오버플로를 일으키거나 부동소수점 계산 방식에 따른 오류를 낼 수 있다

아래 예시들처럼 사용하자

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

 

 

 

 

 

 

댓글