Comparable 인터페이스란?
Comparable
인터페이스는 객체를 정렬하는데 사용되는 메서드인compareTo
를 정의하고 있다.Comparable
인터페이스를 구현한 클래스는 반드시compareTo
를 정의해야 한다.
Comparable 인터페이스 특징
- 자바에서 같은 타입의 인스턴스를 비교해야만 하는 클래스들은 모두
Comparable
인터페이스를 구현하고 있다. Boolean
타입을 제외한 래퍼 클래스와 알파벳, 연대같이 순서가 명확한 클래스들은 모두 정렬이 가능하다.- 기본 정렬 순서는 작은 값에서 큰 값으로 정렬되는 오름차순이다.
Comparable 인터페이스 구현
- Comparable 을 구현했다는 것은 그 클래스의 인스턴스들에는 자연적인 순서가 있음을 의미한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class Car implements Comparable<Car> {
private static final String SPACE = " ";
private static final String MODEL_YEAR = " 식 ";
private String modelName;
private int modelYear;
private String color;
public Car(final String modelName, final int modelYear, final String color) {
this.modelName = modelName;
this.modelYear = modelYear;
this.color = color;
}
@Override
public String toString() {
return this.modelYear + MODEL_YEAR + this.modelName + SPACE + this.color;
}
@Override
public int compareTo(Car car) {
return Integer.compare(this.modelYear, car.modelYear);
}
}
public class Main {
public static void main(String[] args) {
Car car1 = new Car("그렌저", 2016, "검정색");
Car car2 = new Car("쏘나타", 2020, "흰색");
System.out.println(car1.compareTo(car2));
}
}
// 실행결과
-1 // car2 의 연식이 더 크기 때문에
알파벳, 숫자, 연대 같이 순서가 명확한 값 클래스를 작성한다면 반드시 Comparable 인터페이스를 구현하자.
- Comparable 인터페이스를 구현함으로써 그 인스턴스들을 쉽게 정렬하고, 검색하고, 비교 기능을 제공하는 컬렉션(Collection)과 어우러지도록 해야한다.
- 그리고 compareTo 메서드에서 필드의 값을 비교할 떄
<
와>
연산자는 지양해야 한다. 그 대신 박싱된 기본 타입 클래스가 제공하는 정적compare
메서드나Comparator
가 제공하는 비교자 생성 메서드를 이용하자.- ex.
Integer.compare(o1.hashCode(), o2.hashCode())
,Short.compare(value1, value2)
- 관계 연산자(
<
와>
)는 오류를 유발할 가능성이 있다.
- ex.
compareTo 메서드 일반규약
- 이 객체와 주어진 객체의 순서를 비교한다. 이 객체가 주어진 객체보다 작으면 음의정수를 같으면 0을, 크면 양의 정수를 반환한다. 비교할 수 없는 타입이 주어지면
ClassCastException
을 던진다. - 만약, 기존 클래스를 확장한 구체 클래스가 값 필드를 추가했다면
compareTo
규약을 준수할 방법이 없다. 따라서 이런 경우조합(Composition)
을 이용해 우회적으로 규약을 준수하자. 대칭성
,추이성
,반사성
,equals
과 관련하여 설명하고 있는데 이와 관련해선 여기를 참고하자.
equals와 compareTo의 차이
compareTo
와 equals
가 일관되지 않는 BigDecimal 클래스를 사용한 예시를 살펴보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
...
final BigDecimal bigDecimal1 = new BigDecimal("1.0");
final BigDecimal bigDecimal2 = new BigDecimal("1.00");
final HashSet<BigDecimal> hashSet = new HashSet<>();
hashSet.add(bigDecimal1);
hashSet.add(bigDecimal2);
System.out.println(hashSet.size());
final TreeSet<BigDecimal> treeSet = new TreeSet<>();
treeSet.add(bigDecimal1);
treeSet.add(bigDecimal2);
System.out.println(treeSet.size());
...
// 실행결과
hashSet: 2
treeSet: 1
HashSet
과TreeSet
은 서로 다른 메서드로 객체의 동치성을 비교한다.HashSet
은equals를 기반으로 비교
하기 때문에 추가된 두 BigDeciaml이 다른값으로 인식되어 크기가2
가 된다.- 반면에,
TreeSet
은compareTo를 기반
으로 객체에 대한동치성
을 비교하기 때문에 같은값으로 인식되어compareTo
가0
을 반환하기 때문에 크기가1
이 된다.
CompareTo 메서드 작성 요령
Comparable
은 타입을 인수로 받는 제네릭 인터페이스이므로compareTo
의 인수타입은 컴파일 시에 정해지기 때문에 입력 인수 확인이나 형변환을 할 필요가 없다.null
을 인수로 넣으면NullPointerException
을 던져야한다.compareTo
는 동치가 아닌 순서를 비교한다.- 객체 참조 필드를 비교하려면
compareTo
메서드를 재귀적으로 호출한다. Comparable
을 구현하지 않은 필드나 표준이 아닌 순서로 비교해야 한다면Comparator
을 대신 사용한다.
클래스에 핵심 필드 여러개일때 비교
- 클래스에 핵심필드가 여러개라면 가장 핵심적인 필드부터 비교하자.
만약 PhoneNumber 클래스처럼 핵심 필드가 여러 개라면 아래 예시와 같이 핵심적인 필드부터 비교하면 된다.
1
2
3
4
5
6
7
8
9
10
11
12
public int compare(final PhoneNumber phoneNumber) {
int result = Short.compare(areaCode, phoneNumber.areaCode);
if (result == 0) {
result = Short.compare(prefix, phoneNumber.prefix);
if (result == 0) {
result = Short.compare(lineNum, phoneNumber.lineNum);
}
}
return result;
}
비교자 생성 메서드
여기 포스팅을 참고하자.