Comparable
Arrays.sort()와 Collections.sort()로 정렬이 가능한 이유는, 해당 자료구조에 Comparable이 구현되어 있기 때문이다.
Comparable도 equals와 같이 반사성, 대칭성, 추이성을 충족해야 한다.
반사성: x.compareTo(y) == -y.compareTo(x) 여야 한다.
추이성: x.compareTo(y) >0 이며 y.compareTo(z) >0 라면 x.compareTo(z) >0 여야 한다.
대칭성: 크기가 같은 객체간에는 어떤 객체와 비교하더라도 같아야 한다.
equals를 오버라이드 할 때와 마찬가지로, Comparable을 구현할 때도
상속받아 구체화 한 클래스에서 Comparable을 유지할 방법이 없기에
상속 대신 컴포지션을 사용하여, 생성자에서 기본 클래스를 주입받아 equals를 재정의 하는 식으로 구현한다.
또한 compareTo로 시행한 동치성 검사의 결과가 equals와 동일한 결과를 내도록 구현해야 한다.
자료구조에 따라 동일 여부를 compareTo로 하는 것과 equals로 하는 것이 있기 때문이다.
예시로 정확성을 위한 자료구조 BigDecimal은
new BigDecimal("1.0") 과 new BigDecimal("1.00") 을 equals로 비교할 때 서로 다르다.
해당 자료구조는 수의 크기, 소수점 이하 자릿수 scale필드를 모두 관리하기에 equals로는 둘중 하나가 다르면 다르다.
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.TreeMap;
public class Sort {
public static void main(String[] args) {
BigDecimal bigDecimal1 = new BigDecimal("1.0");
BigDecimal bigDecimal2 = new BigDecimal("1.00");
System.out.println(bigDecimal1.compareTo(bigDecimal2)); // 0
System.out.println(bigDecimal1.equals(bigDecimal2)); // false
TreeMap<BigDecimal, Integer> treeMap = new TreeMap<>();
treeMap.put(bigDecimal1,1);
treeMap.put(bigDecimal2,2);
HashMap<BigDecimal, Integer> hashMap = new HashMap<>();
hashMap.put(bigDecimal1,1);
hashMap.put(bigDecimal2,2);
System.out.println(treeMap.size()); // 1
System.out.println(hashMap.size()); // 2
}
}
treeMap에서는 compareTo를 기반으로, hashMap에서는 equals를 기반으로 동치성을 검사하기에
위와 같은 결과를 얻는다.
이러한 상황에서의 차이를 방지하기 위해 equals와 compareTo간 동치성 검사를 일치시킬 필요가 있다.
package Compare;
public class Score implements Comparable<Score> {
public int Eng;
public int Math;
public int total;
public Score(int Eng,int Math){
this.Eng=Eng;this.Math=Math;this.total=Eng+Math;
}
@Override
public int compareTo(Score o) {
int result=Integer.compare(total,o.total);
if(result==0){
result=Integer.compare(Eng,o.Eng);
if(result==0){
result=Integer.compare(Math,o.Math);
}
}
return result;
}
}
Comparable를 구현하며 오버라이드하는 compareTo는 위와 같이 구현할 수 있다.
가장 중요한 항목을 기준으로 비교를 실시하고 그가 동일하면 우선순위가 높은 순서로 비교해나간다.
private static final Comparator<Score> COMPARATOR=
comparingInt((Score o) -> o.total)
.thenComparingInt(o -> o.Eng)
.thenComparingInt(o -> o.Math);
@Override
public int compareTo(Score o){
return COMPARATOR.compare(this, o);
}
메서드체이닝이 가능하며 primitive type을 직접 비교해서 박싱, 언박싱이 일어나지 않아 성능도 준수한 방식으로
위와같이 Comparator의 메서드를 사용하여 구현도 가능하다.