이펙티브 자바, 쉽게 정리하기 - item 14. Comparable을 구현할지 고려하라
Comparable
이란?
- 믹스인 인터페이스이다.
- 유일한 구현 메서드인
compareTo
에 같은 객체끼리의natrual order
를 정의한다.
- 유일한 구현 메서드인
equals()
와 같이 동치도 비교하며, 순서도 비교하니 업그레이드된 버전이다.
Comparable
구현의 이점
Comparable
을 구현한 객체의 배열은 쉽게 정렬 가능하다.Arrays.sort(a)
Collection
객체들에서도 정렬을 활용할 수 있다.TreeSet
자료구조 같은 경우,Comparable
을 구현한 타입만 제너릭으로 받을 수 있다.String
타입을 넣는 경우, 들어간 모든 문자열을 알파벳순으로 출력 가능하다.
natrual order
만 정의해줄 뿐인데, 어마어마한 부가 효과가 많다.
반대로 구현하지 않았을 경우, 어마어마한 부가 효과를 못 누린다는 뜻이다.
compareTo
메서드의 일반 규약은?
- 주어진 객체를 기준으로하여 아래와 같은 값을 반환한다.
- 비교대상보다 작으면 음의 정수(-1)
- 비교대상과 같으면 0
- 비교대상보다 크면 양의 정수(1)
sgn
은 부호 함수(signum function)를 의미하며, 음수, 0, 양수일 때 각각-1
,0
,1
로 표현하도록 하였다.
Comparable
을 구현한 클래스는- 모든 x, y에 대해
sgn(x.compareTo(y)) == -sgn(y.compareTo(x))
여야 한다.- 예외도
x.compareTo(y)
와y.compareTo(x)
가 동일하게 터져야 한다.
- 예외도
- 추이성(transitivity)을 보장해야 한다.
(x.compareTo(y) > 0 && y.compareTo(z) > 0)
이면,x.compareTo(z) > 0
이여야 한다.0
보다 크다는 것은 비교 대상보다 크다는 것이다.x > y > z
인 경우에x > z
여야 한다는 뜻이다.
x.compareTo(y) == 0
이면,sgn(x.compareTo(z)) == sgn(y.compareTo(z))
여야 한다.x == y
일 때,x == z && y == z
여야 한다는 뜻이다.
(x.compareTo(y) == 0) == (x.equals(y))
는 꼭 지켜야하는 것은 아니지만 권고사항이다.equals()
와 논리적 동치를 판단하는 기준이 같다는 뜻이다.- 이를 지키지 않는다면 '주의: 이 클래스의 순서는
equals()
와 일관되지 않습니다.' 라고 써주는 것이 좋다. - 이를 지키지 않으면, 컬렉션 인터페이스(
Collection
,Set
,Map
)에서 정의된 동작과 엇박자를 낼 수 있다. 정렬된 컬렉션은 동치를 비교할 때equals()
대신compareTo()
를 사용한다.
- 모든 x, y에 대해
정리하자면 반사성, 대칭성, 추이성을 지켜야 한다는 뜻이다.
equals()
와 달리 타입이 다른 객체에 대해서는 신경 안 써도 된다.equals()
와 같이 상속으로는 이러한 일반규약을 다 지킬 방법이 없고, '사용'형태로 객체 안에 사용할 필드를 두는 것이 낫다.
Comparable
구현하고 활용하기
CaseInsensitiveString
예제
static final class CaseInsensitiveString implements Comparable<CaseInsensitiveString>{
String s;
public CaseInsensitiveString(String s) {
this.s = s;
}
@Override
public int compareTo(CaseInsensitiveString o) {
return String.CASE_INSENSITIVE_ORDER.compare(s, o.s);
}
@Override
public String toString() {
return "CaseInsensitiveString{" +
"s='" + s + '\'' +
'}';
}
}
@Test
public void CaseInsensitiveStringTest1() {
CaseInsensitiveString cis1 = new CaseInsensitiveString("Apple");
CaseInsensitiveString cis2 = new CaseInsensitiveString("blue");
CaseInsensitiveString cis3 = new CaseInsensitiveString("abuse");
CaseInsensitiveString cis4 = new CaseInsensitiveString("Cream");
TreeSet<CaseInsensitiveString> cisList = new TreeSet<>();
cisList.add(cis1);
cisList.add(cis2);
cisList.add(cis3);
cisList.add(cis4);
String s = cisList.toString();
System.out.println("s = " + s);
}
@Test
public void CaseInsensitiveStringTest2() {
CaseInsensitiveString cis1 = new CaseInsensitiveString("Apple");
CaseInsensitiveString cis2 = new CaseInsensitiveString("blue");
CaseInsensitiveString cis3 = new CaseInsensitiveString("abuse");
CaseInsensitiveString cis4 = new CaseInsensitiveString("Cream");
ArrayList<CaseInsensitiveString> cisList = new ArrayList<>();
cisList.add(cis1);
cisList.add(cis2);
cisList.add(cis3);
cisList.add(cis4);
Collections.sort(cisList);
System.out.println("cisList = " + cisList);
}
실행결과
s = [CaseInsensitiveString{s='abuse'}, CaseInsensitiveString{s='Apple'}, CaseInsensitiveString{s='blue'}, CaseInsensitiveString{s='Cream'}]
cisList = [CaseInsensitiveString{s='abuse'}, CaseInsensitiveString{s='Apple'}, CaseInsensitiveString{s='blue'}, CaseInsensitiveString{s='Cream'}]
대소문자에 관계없이 정렬되어 나온다.
Comparable
을 구현함으로써,TreeSet
과Collections.sort()
라는 강력한 정렬 기능을 자유롭게 이용할 수 있게 됐다.compareTo()
의 내부 구현은 java의 기본 래퍼 클래스가 제공하는 정적메서드compare()
를 이용하면 된다.
PhoneNumber
예제
static class PhoneNumber implements Comparable<PhoneNumber>{
public final Short areaCode;
public final Short prefix;
public final Short lineNum;
// ...
@Override
public int compareTo(PhoneNumber o) {
int result = Short.compare(areaCode, o.areaCode);
if(result == 0) result = Short.compare(prefix, o.prefix);
if(result == 0) result = Short.compare(lineNum, o.lineNum);
return result;
}
}
- 비교할 대상이 여러개일 때는 위와 같이 차례차례 비교하면 된다.
자바 8 방식으로 짜보기
@Override
public String toString() {
return String.format("%03d-%03d-%04d", areaCode, prefix, lineNum);
}
private static final Comparator<PhoneNumber> COMPARATOR =
comparing((PhoneNumber pn) -> pn.areaCode)
.thenComparing(pn -> pn.prefix)
.thenComparing(pn -> pn.lineNum);
@Override
public int compareTo(PhoneNumber o) {
return COMPARATOR.compare(this, o);
}
phoneNumbers = [000-1111-2222, 000-2222-1111, 111-000-0000]
- 자바8에서는
Comparator
인터페이스가 비교자 생성 메서드(comparator construction method)와 팀을 꾸려 메서드 연쇄 방식으로 비교자를 생성할 수 있다. - 가독성이 매우 좋다.
- 단, 약간의 성능 저하가 뒤따른다.
정리
- 순서가 있는 클래스를 작성한다면,
Comparable
인터페이스를 구현하는 것이 좋다. compareTo
메서드를 구현할 때는 박싱 클래스에서 제공하는compare()
를 적극 활용하자.
'Java > 이펙티브 자바' 카테고리의 다른 글
이펙티브 자바, 쉽게 정리하기 - item 16. public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라 (0) | 2022.01.01 |
---|---|
이펙티브 자바, 쉽게 정리하기 - item 15. 클래스와 멤버의 접근 권한을 최소화하라 (0) | 2022.01.01 |
이펙티브 자바, 쉽게 정리하기 - item 13. clone 재정의는 주의해서 진행하라 (0) | 2021.12.30 |
이펙티브 자바, 쉽게 정리하기 - item 12. toString을 항상 재정의하라 (0) | 2021.12.30 |
이펙티브 자바, 쉽게 정리하기 - item 11. equals를 재정의하려거든 hashCode도 재정의하라 (0) | 2021.12.29 |