아이템14. Comparable을 구현할지 고려하라
1 2 3 4 5 6 7 public class WordList { public static void main (String[] args) { Set<String> s = new TreeSet <>(); Collections.addAll(s, args); System.out.println(s); } }
compareTo 메서드의 일반 규약
equals와 거의 비슷하기 때문에 생략 (equals와 다른점만 확인)
표현식의 값이 음수, 0, 양수일 때 -1, 0, 1을 반환하도록 정의
타입이 다른 객체를 신경 쓰지 않아도 된다.
타입이 다르면 간단히 ClassCastException을 던진다.
다른 타입 사이의 비교도 허용한다. (공통 인터페이스를 가질 때)
교재 88p 참조
compareTo 규약을 지키지 못하면 비교를 활용하는 클래스와 어울리지 못한다. (TreeSet, TreeMap)
equals와 동일하게 반사성, 대칭성, 추이성을 충족해야 한다.
일반 규약 요약
두 객체 참조의 순서를 바꿔 비교해도 예상한 결과가 나와야 한다.
첫 번째가 두 번째보다 크고 두 번째가 세 번째보다 크면, 첫 번째는 세 번째보다 커야 한다.
크기가 같은 객체들끼리는 어떤 객체와 비교하더라도 항상 같아야 한다. (필수는 아니지만 지키면 좋음)
주의점
Comparable은 타입을 인수로 받는 제네릭 인터페이스이므로 compareTo 메서드의 인수 타입은 컴파일타임에 정해진다.
입력 인수의 타입을 확인하거나 형변환할 필요 없음 (바로 빨간줄로 보이니까!!)
null을 인수에 넣으면 NullPointerException
각 필드가 동치인지를 비교하는 게 아니라 그 순서를 비교한다.
비교자가 하나인 compareTo 1 2 3 4 5 public final class CaseInsensitiveString implements Comparable <CaseInsensitiveString> { public int compareTo (CaseInsensitiveString cis) { return String.CASE_INSENSITIVE_ORDER.compare(s, cis.s); } }
자바7 이후 변경 상황
compareTo 메서드에서 관계 연산자 <와 >를 사용하는 이전 방식은 거추장스럽고 오류를 유발하니, 이제는 추천 X
자바 8에서 객체를 비교하는 방식
간결하고 성능 조금 안좋아지는 방식
1 2 3 4 5 6 7 8 private static final Comparator<PhoneNumer> COMPARATOR = comparingInt((PhoneNumber pn) -> pn.areaCode) .thenComparingInt(pn -> pn.prefix) .thenComparingInt(pn -> pn.lineNum); public int compareTo (PhoneNumber pn) { return COMPARATOR.compare(this , pn); }
comparingInt는 객체 참조를 int 타입 키에 매핑하는 키 추출 함수를 인수로 받아, 그 키를 기준으로 순서를 정하는 비교자를 반환하는 정적 메서드
객체 참조용 비교자 생성 메서드도 준비됨
comparing이라는 정적 메서드 2개가 다중정의되어 있다.
첫번째 메서드: 키 추출자를 받아서 그 키의 자연적 순서를 사용한다.
두번째 메서드: 키 추출자 하나와 추출된 키를 비교할 비교자까지 총 2개의 인수를 받는다.
또한, thenComparing이란 인스턴스 메서드가 3개 다중정의되어 있다.
그냥 comparing을 찾아와보자
Comparator 인터페이스를 가져와봤다.
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 package java.util;import java.io.Serializable;import java.util.function.Function;import java.util.function.ToIntFunction;import java.util.function.ToLongFunction;import java.util.function.ToDoubleFunction;import java.util.Comparators;@FunctionalInterface public interface Comparator <T> { int compare (T o1, T o2) ; boolean equals (Object obj) ; default Comparator<T> reversed () { return Collections.reverseOrder(this ); } default Comparator<T> thenComparing (Comparator<? super T> other) { Objects.requireNonNull(other); return (Comparator<T> & Serializable) (c1, c2) -> { int res = compare(c1, c2); return (res != 0 ) ? res : other.compare(c1, c2); }; } default <U> Comparator<T> thenComparing ( Function<? super T, ? extends U> keyExtractor, Comparator<? super U> keyComparator) { return thenComparing(comparing(keyExtractor, keyComparator)); } default <U extends Comparable <? super U>> Comparator<T> thenComparing ( Function<? super T, ? extends U> keyExtractor) { return thenComparing(comparing(keyExtractor)); } default Comparator<T> thenComparingInt (ToIntFunction<? super T> keyExtractor) { return thenComparing(comparingInt(keyExtractor)); } default Comparator<T> thenComparingLong (ToLongFunction<? super T> keyExtractor) { return thenComparing(comparingLong(keyExtractor)); } default Comparator<T> thenComparingDouble (ToDoubleFunction<? super T> keyExtractor) { return thenComparing(comparingDouble(keyExtractor)); } public static <T extends Comparable <? super T>> Comparator<T> reverseOrder () { return Collections.reverseOrder(); } @SuppressWarnings("unchecked") public static <T extends Comparable <? super T>> Comparator<T> naturalOrder () { return (Comparator<T>) Comparators.NaturalOrderComparator.INSTANCE; } public static <T> Comparator<T> nullsFirst (Comparator<? super T> comparator) { return new Comparators .NullComparator<>(true , comparator); } public static <T> Comparator<T> nullsLast (Comparator<? super T> comparator) { return new Comparators .NullComparator<>(false , comparator); } public static <T, U> Comparator<T> comparing ( Function<? super T, ? extends U> keyExtractor, Comparator<? super U> keyComparator) { Objects.requireNonNull(keyExtractor); Objects.requireNonNull(keyComparator); return (Comparator<T> & Serializable) (c1, c2) -> keyComparator.compare(keyExtractor.apply(c1), keyExtractor.apply(c2)); } public static <T, U extends Comparable <? super U>> Comparator<T> comparing ( Function<? super T, ? extends U> keyExtractor) { Objects.requireNonNull(keyExtractor); return (Comparator<T> & Serializable) (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2)); } public static <T> Comparator<T> comparingInt (ToIntFunction<? super T> keyExtractor) { Objects.requireNonNull(keyExtractor); return (Comparator<T> & Serializable) (c1, c2) -> Integer.compare(keyExtractor.applyAsInt(c1), keyExtractor.applyAsInt(c2)); } public static <T> Comparator<T> comparingLong (ToLongFunction<? super T> keyExtractor) { Objects.requireNonNull(keyExtractor); return (Comparator<T> & Serializable) (c1, c2) -> Long.compare(keyExtractor.applyAsLong(c1), keyExtractor.applyAsLong(c2)); } public static <T> Comparator<T> comparingDouble (ToDoubleFunction<? super T> keyExtractor) { Objects.requireNonNull(keyExtractor); return (Comparator<T> & Serializable) (c1, c2) -> Double.compare(keyExtractor.applyAsDouble(c1), keyExtractor.applyAsDouble(c2)); } }
int, long, double을 위한 메소드들이 추가적으로 보인다.
핵심 정리
순서를 고려해야 하는 값 클래스를 작성한다면, 꼭 Comparable 인터페이스를 구현하자
Comparable 인터페이스를 구현하면, 그 인스턴스들을 쉽게 정렬하고, 검색하고, 비교 기능을 제공하는 컬렉션과 어우지도록 해야 한다.
compareTo 메서드는 박싱된 기본 타입 클래스가 제공하는 정적 compare 메서드나 Comparator 인터페이스가 제공하는 비교자 생성 메서드를 사용하자.