정렬 또는 자동 정렬되는 컬렉션에서 굉장히 유용하게 사용할 수 있다. 즉 클래스에 Comparable을 구현한다는 것은 클래스에게 natural order (기본 대소 비교 기능)을 제공해주는 것이라 할 수 있다.
적은 노력으로 굉장히 큰 이득을 얻을 수 있기 때문에 클래스를 대소 비교에 활용해야 된다면 구현하도록 하자.
public interface Comparable<T> {
public int compareTo(T o);
}
Comparable은 제네릭 형태로 compareTo 메서드를 구현해야한다.
compareTo는 int 형으로 -1, 0, 1 이렇게 3가지 영역으로 구분하는 데 수학에서는 이를 sgn식 표기라 하며 부호 함수라 한다.
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 문을 활용한 방법이 있다.
장점은 성능이 좋다.
단점은 조건이 많아지면 코드가 지저분해질 수 있다.
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% 정도의 성능 손해가 있다고 한다. 하지만 조건이 길어진다면 좀 더 한눈에 보기 좋게 코드를 짤 수 있다.
// 문제의 코드
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