peaches-book-study / effective-java

이펙티브 자바 3/E
0 stars 2 forks source link

Item 14. Comparable을 구현할지 고려하라. #13

Open heon118 opened 7 months ago

heon118 commented 7 months ago

Chapter : 3. 모든 객체의 공통 메서드

Item : 14. Comparable을 구현할지 고려하라.

Assignee : heon118


🍑 서론

Comparable 인터페이스의 메서드인 compareTo compareTo는 단순 동치성과 순서의 비교가 가능하며 제네릭하다. Comparable을 구현했다는 것은 해당 클래스의 인스턴스들에는 자연적인 순서가 있음을 뜻하기에 Comparable을 구현한 객체들의 배열은 아래와 같이 쉽게 정렬할 수 있다.

Arrays.sort(a);

검색, 극단값 계산, 자동 정렬되는 컬렉션 관리도 쉽게 할 수 있다. String이 Comparable을 구현한 덕분에 아래에서 중복 제거 및 알파벳 순 출력이 가능하다.

public class WordList {
    public static void main(String[] args) {
        Set<String> s = new TreeSet<>();
        Collections.addAll(s, args);
        System.out.println(s);
    }
}

자바 플랫폼 라이브러리의 모든 값 클래스와 열거 타입(Item 34)이 Comparable을 구현했다. 알파벳, 숫자, 연대 같이 순서가 명확한 값 클래스를 작성한다면 반드시 Comparable 인터페이스를 구현하자.

🍑 본론

public interface Comparable<T> {
    int compareTo(T t);
}

compareTo 메서드의 일반 규약

이 객체와 주어진 객체의 순서 비교
이 객체가 주어진 객체보다 작으면 음의 정수, 같으면 0, 크면 양의 정수를 반환한다.
비교 불가능한 타입의 객체가 주어지면 ClassCastException을 던진다.

다음의 sgn(표현식) 표기는 수학에서 말하는 부호함수(signum function)를 뜻하며, 표현식의 값이 음수, 0, 양수일 때 각각 -1, 0, 1을 반환하도록 정의했다.

  • Comparable을 구현한 클래스는 모든 x, y에 대해 sgn(x.compareTo(y)) == -sgn(y.compareTo(x))여야 한다.(따라서 x.compareTo(y)는 y.compareTo(x)가 예외를 던질 때에 한해 예외를 던져야 한다. -> "대칭성 : 두 객체 참조의 순서를 바꿔 비교해도 예상한 결과가 나와야 한다."

위 세 규약은 compareTo 메서드로 수행하는 동치성 검사도 equals 규약과 똑같이 반사성, 대칭성, 추이성을 충족해야함을 뜻한다. 따라서 주의사항도, 우회법도 같다.(Item 10) 기존 Comparable을 구현한 클래스를 확장해 새로운 값 컴포넌트를 추가하고 싶다면, 확장하는 대신 독립된 클래스를 만들고 이 클래스에 원래 클래스의 인스턴스를 가리키는 필드를 두면 된다. 그리고 내부 인스턴스를 반환하는 '뷰' 메서드를 제공하면 된다.

// equal 비교 : false // compareTo 비교 : 0 // HashSet의 원소 개수 : 2 // TresSet의 원소 개수 : 1


### compareTo 메서드 작성
compareTo 메서드는 각 필드의 순서를 비교한다.
객체 참조 필드를 비교하려면 compareTo 메서드를 재귀적으로 호출해야 한다.
Comparable을 구현하지 않은 필드나 표준이 아닌 순서로 비교해야 한다면 비교자(Comparator)를 대신 사용한다. 비교자는 직접 만들거나 자바가 제공하는 것 중 골라 사용하면 된다. 아래 코드는 자바가 제공하는 비교자를 사용한다.
(Item 10의 코드 참조)
```java
// 기본 타입 필드가 하나뿐인 비교자
public final class CaseInsensitiveString implements Comparable<CaseInsensitiveString> {
    @Override
    public int compareTo(CaseInsensitiveString cis) {
        return String.CASE_INSENSITIVE_ORDER.compare(s, cis.s);
    }
    private final String s;
    public CaseInsensitiveString(String s) {
        this.s = Objects.requireNonNull(s);
    }
}

이전에는 정수 기본 타입 필드를 비교할 때 관계연산자 <, >와 실수 기본 타입 필드를 비교할 때는 정적 메서드인 Double.compare, Float.compare를 사용하는 것을 권했다. 하지만 Java7부터 박싱된 기본 타입 클래스들에 새로 추가된 정적 메서드인 compare를 이용하면 된다. 관계연산자 <, >를 사용하는 방식은 거추장스럽고 오류를 유발하기에 추천하지 않는다.

클래스에 핵심 필드가 여러 개라면 비교 순서가 중요해진다.(Item 10의 코드 참조)

// 기본 타입 필드가 여럿일 때의 비교자
    public int compareTo(PhoneNumber pn){
        int result = Short.compare(areaCode, pn.areaCode);      // 가장 중요한 필드
        if(result == 0) {
            result = Short.compare(prefix, pn.prefix);          // 두 번째로 중요한 필드
            if(result == 0)
                result = Short.compare(lineNum, pn.lineNum);    // 세 번째로 중요한 필드
        }
        return result;
    }

메서드 연쇄 방식으로 비교자를 생성하여 compareTo 메서드를 구현할 수 있다. (간결하지만 약간의 성능 저하가 뒤따른다. PhoneNumber 인스턴스의 정렬된 배열에 적용해보니 10%정도 느려졌다. -> 이거 이해안됨) 자바의 정적 임포트 기능을 이용하면 정적 비교자 생성 메서드들을 이름만으로 사용할 수 있어 코드가 훨씬 깔끔해진다.

// 비교자 생성 메서드를 활용한 비교자
import static java.util.Comparator.comparingInt; // import 추가

    private static final Comparator<PhoneNumber> COMPARATOR =
            comparingInt((PhoneNumber pn) -> pn.areaCode)
                    .thenComparingInt(pn -> pn.prefix)
                    .thenComparingInt(pn -> pn.lineNum);

    public int compareTo(PhoneNumber pn) {
        return COMPARATOR.compare(this, pn);
    }

Comparator에는 많은 보조 생성 메서드들이 있다.

// 비교자 생성 메서드를 활용한 비교자
    static Comparator<Object> hashCodeOrder =
            Comparator.comparingInt(o -> o.hashCode());"

🍑 결론

순서를 고려해야 하는 값 클래스를 작성한다면 꼭 Comparable 인터페이스를 구현하여, 그 인스턴스들을 쉽게 정렬하고, 비교 기능을 제공하는 컬렉션과 어우러지도록 해야 한다. compareTo 메서드에서 필드의 값을 비교할 때 <> 연산자 대신 박싱된 기본 타입 클래스가 제공하는 정적 compare 메서드나 Comparator 인터페이스가 제공하는 비교자 생성메서드를 사용한다.


Referenced by

youngkimi commented 6 months ago
image

오타 고쳐주세요 :)

youngkimi commented 6 months ago
image

이것도 고쳐주세요 :)

youngkimi commented 6 months ago
image

수정해주세요 :)