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;
}
비교자 생성 메서드
여기 포스팅을 참고하자.