이펙티브 자바 #item14 Comparable을 구현할지 고려해라

임현규·2023년 1월 12일
0

이펙티브 자바

목록 보기
14/47
post-thumbnail

Comparable의 장점 및 용도

정렬 또는 자동 정렬되는 컬렉션에서 굉장히 유용하게 사용할 수 있다. 즉 클래스에 Comparable을 구현한다는 것은 클래스에게 natural order (기본 대소 비교 기능)을 제공해주는 것이라 할 수 있다.

적은 노력으로 굉장히 큰 이득을 얻을 수 있기 때문에 클래스를 대소 비교에 활용해야 된다면 구현하도록 하자.

Comparable의 구성

public interface Comparable<T> {

    public int compareTo(T o);
}

Comparable은 제네릭 형태로 compareTo 메서드를 구현해야한다.

compareTo는 int 형으로 -1, 0, 1 이렇게 3가지 영역으로 구분하는 데 수학에서는 이를 sgn식 표기라 하며 부호 함수라 한다.

Comparable, compareTo 일반 규약

  1. sgn(x.compareTo(y)) == -sgn(y.compareTo(x))
  2. Comparable은 추이성을 보장해야함
  3. x.compareTo(y) == 0 이면 sgn(x.compareTo(z)) == sgn(y.compareTo(z))

1번을 살펴보면 역인 경우 sgn을 반전 시키면 기존과 같아야 한다는 것이다. 이를 통해 대소 관계를 보장한다.

2번의 추이성은 3단 논법을 생각해보자..
a < b, b < c => a ? c 의 관계는 ??? 당연히 a < c 이다. 이런 관계를 충족해야한다.

3번은 sgn 결과가 0이라면 두 객체는 크기가 동등하다는 것이다. 그렇기에 x.compare(z)와 y.compare(z)의 sgn 값은 동일한 것이다.

여러개의 필드를 활용해 대소를 비교하는 경우

직접 비교하기

public class Student implements Comparable<Student> {

    private final int classNo;
    private final String name;


    public Student(int classNo, String name) {
        this.classNo = classNo;
        this.name = name;
    }

    @Override
    public int compareTo(@NotNull Student o) {
        if (classNo == o.classNo) {
            return name.compareTo(o.name);
        }
        return Integer.compare(classNo, o.classNo);
    }
}

학생 클래스는 학번과 이름을 오름차순으로 natural order를 만들고 싶었다. 우선 순위는 학번이다. 이런 경우 if 문을 활용한 방법이 있다.

장점은 성능이 좋다.
단점은 조건이 많아지면 코드가 지저분해질 수 있다.

Comparator 활용하기

public class Student implements Comparable<Student> {

    private final int classNo;
    private final String name;

    private static final Comparator<Student> COMPARATOR =
        Comparator.comparingInt((Student student) -> student.classNo)
            .thenComparing(student -> student.name);


    public Student(int classNo, String name) {
        this.classNo = classNo;
        this.name = name;
    }

    @Override
    public int compareTo(@NotNull Student o) {
        return COMPARATOR.compare(this, o);
    }
}

조건이 많아져도 stream과 같이 .형태로 이어서 comparing을 사용한다. 책에서는 10% 정도의 성능 손해가 있다고 한다. 하지만 조건이 길어진다면 좀 더 한눈에 보기 좋게 코드를 짤 수 있다.

Comparable CompareTo를 구현시 유의점

// 문제의 코드
public int compare(Object o1, Object o2) {
	return o1.hashCode() - o2.hashCode();
}

'-' 이용해 둘 간의 크기 차이를 통해 compareTo를 정의하지 말자. 만약 큰 타입이거나 아주 작은 타입을 비교할 때 정수 오버플로 및 부동 소수점 계산 방식에 따른 오류를 낼 수도 있다. 심지어 월등히 빠르지도 않다.

또 '-' 사용보다 데이터 타입이 제공하는 compare가 가지는 좋은 이점은 내림차순, 오름차순을 생각할 때 덜 헷갈린다.

compare는 오름차순을 보장하기 때문에 내림차순을 사용하려는 경우 -만 앞에 붙이면 되기 때문에 사용이 쉽다.

// 좋은 코드
public int compare(Object o1, Object o2) {
	return Integer.compare(o1, o2);
}

comapare(x, y)
'>' -> 1
'==' -> 0
'<' -> -1

profile
엘 프사이 콩그루

0개의 댓글