자바에서 객체를 정렬할 때 가장 중요한 것은 "누가 더 큰지 판별하는 기준"을 정하는 것이다. 정렬 알고리즘이 아무리 훌륭해도, 객체 간의 우위를 가릴 수 없다면 줄을 세울 수 없기 때문이다.
이 "판별 기준"을 제공하는 두 가지 핵심 인터페이스, Comparable과 Comparator에 대해 정리해보자.
두 인터페이스의 가장 쉬운 구별법은 누가 비교하는가? 이다.
implements Comparable)int compareTo(T o)int compare(T o1, T o2)
Comparable은 객체 스스로에게 "비교할 수 있는 능력"을 부여하는 것이다. 보통 Integer, String 같은 클래스들이 기본적으로 구현하고 있다.
public interface Comparable<T> {
// 나 자신(this)과 상대방(o)을 비교
int compareTo(T o);
}
정렬 알고리즘(시스템)은 compare 메서드가 반환하는 int값의 부호를 보고 두 객체의 위치를 바꿀지 말지 결정한다. 가장 중요한 핵심은 "양수면 자리를 바꾼다(Swap)"는 것이다.
this)이 크다compare()와 comareTo()는 두 객체의 비교결과를 반환하도록 작성한다.반환값을 구할 때 누구에서 누구를 빼야 할지 헷갈린다면 아래 공식을 기억하자.
// this(앞) - o(뒤)
return this.value - o.value;
(뒤의 값) - (앞의 값)// o(뒤) - this(앞)
return o.value - this.value;
단순 뺄셈(-) 방식은 값의 차이가 int 범위를 벗어날 경우 정렬이 꼬일 수 있다. 실무에서는 안전하게 compare 메서드를 사용하는 것을 권장한다.
// 권장 방식
return Integer.compare(this.value, o.value);
public final calss Integer extends Number implements Comparable {
...
public int compareTo(Integer anotherInteger) {
int v1 = this.value;
int v2 = anotherInteger.value;
// 같으면 0, 오른쪽 값이 크면 -1, 작으면 1을 반환
return (v1 < v2) ? -1 : (v1 == v2 ? 0 : 1));
...
}
Comparable이 구현되어 있지 않거나, 이미 구현된 기준(오름차순) 말고 다른 기준(내림차순 등)으로 정렬하고 싶을 때 사용합니다.
public interface Comparator<T> {
// 제 3자의 입장에서 o1과 o2를 비교
int compare(T o1, T o2);
// (참고) equals는 모든 객체의 기본 메서드이므로 보통 구현 생략 가능
boolean equals(Object obj);
}
기본 정렬 결과를 뒤집으려면 결과에 -1을 곱하거나, 파라미터의 순서를 바꿔서 비교하면 된다.
class Descending implements Comparator {
public int compare(Object o1, Object o2) {
if (o1 instanceof Comparable && o2 instanceof Comparable) {
Comparable c1 = (Comparable)o1;
Comparable c2 = (Comparable)o2;
// -1을 곱해서 기본 정렬 방식(오름차순)을 뒤집는다.
return c1.compareTo(c2) * -1;
// Tip: c2.compareTo(c1) 이렇게 순서를 바꿔도 내림차순이 된다.
}
return -1;
}
}
Arrays.sort)Arrays.sort()는 매개변수에 따라 적용되는 정렬 기준이 달라진다.
static void sort(Object[])static void sort(Object[] a, Comparator c)class Main {
public static void main(String[] args) throws IOException {
String[] words = {"banana", "apple", "kiwi", "fig"};
// Comparator를 람다식(Lambda)로 구현
// 길이 오름차순 정렬
Arrays.sort(words, (s1, s2) -> {
return s1.length() - s2.length();
});
System.out.println(Arrays.toString(words));
// ["apple", "banana", "fig", "kiwi"]
}
}
정렬의 기준만 제공하면 될까? 정렬 자체를 수행하는 코드는 어디에 있지?
정렬 알고리즘은 자리를 바꾸는 로직(Swap)과 자리를 바꿀지 결정하는 기준(Comparable)로 나뉜다.
아래 버블 정렬 예시를 보면, 정렬 알고리즘(For문)은 그대로인데 비교 로직(if문)만 갈아끼우는 것을 확인할 수 있다.
static void sort(Object[] objArr) {
for (int i = 0; i < objArr.length - 1; i++) {
for (int j = 0; j < objArr.length - 1 - i; j++) {
// Comparable 형변환 후 compareTo 호출
Comparable c = (Comparable) objArr[j];
// "내가 쟤보다 크면(양수) 자리를 바꾼다" -> 오름차순
if (c.compareTo(objArr[j+1]) > 0) {
Object tmp = objArr[j];
objArr[j] = objArr[j+1];
objArr[j+1] = tmp;
}
}
}
}
static void sort(Object[] objArr, Comparator c) {
for (int i = 0; i < objArr.length - 1; i++) {
for (int j = 0; j < objArr.length - 1 - i; j++) {
// 외부 심판(Comparator)에게 비교를 위임
// "심판이 보기에 앞이 뒤보다 크면(양수) 자리를 바꾼다"
if (c.compare(objArr[j], objArr[j+1]) > 0) {
Object tmp = objArr[j];
objArr[j] = objArr[j+1];
objArr[j+1] = tmp;
}
}
}
}
class Ex11_7 {
public static void main(String[] args) {
String[] strArr = {"cat", "Dog", "lion", "tiger"};
// 1. Comparable에 의한 정렬 (기본: 사전순, 대문자 우선)
Arrays.sort(strArr);
System.out.println("strArr=" + Arrays.toString(strArr));
// 결과: [Dog, cat, lion, tiger]
// 2. Comparator에 의한 정렬 (특수: 대소문자 구분 없음)
Arrays.sort(strArr, String.CASE_INSENSITIVE_ORDER);
System.out.println("strArr=" + Arrays.toString(strArr));
// 결과: [cat, Dog, lion, tiger]
// 3. Comparator에 의한 정렬 (특수: 역순)
Arrays.sort(strArr, new Descending());
System.out.println("strArr=" + Arrays.toString(strArr));
// 결과: [tiger, lion, cat, Dog]
}
}
compareTo를 구현해 사용한다.Collections.sort의 2번째 인자로 사용된다.compareTo/compare) 결과가 양수면, 나는 자리를 바꿔줄게