[Effective Java] Item 14. Comparable을 구현할지 고려하라

2024. 3. 10. 17:59Java/Effective Java

반응형

Comparable 인터페이스 내에 선언된 유일한 메서드인 compareTo는 동치성 비교에 더해 순서 비교까지 가지고 있습니다.
Comparable을 구현한 클래스를 담은 배열은 Arrays.sort()를 이용하여 정렬할 수 있습니다.

compareTo 메서드 일반 규약입니다.

  1. 현재객체가 주어진 객체보다 작으면 -1, 같으면 0, 크면 1를 반환합니다. (현재객체와 비교할 수 없는 타입이 주어지는 경우에는 ClassCastException을 반환합니다.)
  2. x.compareTo(y) == -y.compareTo(x)
  3. x.compareTo(y) > 0 && y.compareTo(z) 일 경우, x.compareTo(z) > 0 를 만족해야 합니다.
  4. x.compareTo(y) == 0 일 경우, x.compareTo(z) == y.compareTo(z) 를 만족해야 합니다.
  5. x.compareTo(y) == 0 일 경우, x.equals(y)여야 합니다.

compareTo 규약을 지킬 경우, 비교를 활용한 클래스인 TreeSet, TreeMap, 정렬 등을 활용할 수 있습니다.

정렬된 컬렉션의 경우, 동치성 비교에 equals가 아닌 compareTo 를 사용합니다.


compareTo의 일반 규약중 맨 마지막에 쓰여진, x.compareTo(y) == 0 인 경우, x.equals(y) == true를 만족하도록 정의하자에 대해서 살펴봅시다.

BigDecimal에서 "1.0"과 "1.00" 입력에 대한 compareTo 비교값은 0을 반환하지만, equals 결과는 false를 반환하고 있습니다.

BigDecimal b1 = new BigDecimal("1.0");
BigDecimal b2 = new BigDecimal("1.00");
System.out.println(b1.equals(b2));      // false
System.out.println(b1.compareTo(b2));   // 0

이러한 이유로, HashSet에 넣은 결과와

Set<BigDecimal> hashSet = new HashSet<>();
hashSet.add(b1);
hashSet.add(b2);
System.out.println(hashSet);    // [1.0, 1.00]

TreeSet에 넣은 결과가 달라지게 됩니다.

Set<BigDecimal> treeSet = new TreeSet<>();
treeSet.add(b1);
treeSet.add(b2);
System.out.println(treeSet);    // [1.0]

이 부분에 대해 유의하고, compareTo를 규약에 맞춰 정의하는 필요성에 대해 고려해보는 것이 좋습니다.


정수나 실수 primitive 타입에 대한 비교연산은 < 나 > 같은 관계 연산자를 쓰지 않아야 합니다.
(보이게도 거추장 스럽고 오류를 발생시키기도 합니다.)

compareTo 메서드에서 필드값을 비교할 때에는

  • 박싱된 기본 타입 클래스의 정적 메서드(Float.compare등..)를 이용하거나
  • Comparator 인터페이스가 제공하는 비교자 생성 메서드를 사용합시다.

Comparable 사용 예제

public record Member(
    String name, int age
) implements Comparable<Member> {

    @Override
    public int compareTo(@NotNull Member m) {
        int result = Integer.compare(age, m.age);
        if(result == 0) {
            return name.compareTo(m.name);
        }
        return result;
    }
}
public class ComparableExample {

    public static void main(String[] args) {
        List<Member> members = Lists.newArrayList(new Member("jini", 33),
            new Member("lily", 28), new Member("sol", 39), new Member("coco", 28));
        Collections.sort(members);
        System.out.println(members);
    }
}
[Member[name=coco, age=28], Member[name=lily, age=28], Member[name=jini, age=33], Member[name=sol, age=39]]

Comparator 사용 예

public record Student(
    String name, int age
) {
}
public class ComparatorExample {

    public static void main(String[] args) {
        List<Student> students = Lists.newArrayList(new Student("jini", 33),
            new Student("lily", 28), new Student("sol", 39), new Student("coco", 28));
        Collections.sort(students, COMPARATOR);
        System.out.println(students);
    }

    static final Comparator<Student> COMPARATOR = Comparator
        .comparingInt((Student s) -> s.age())
        .thenComparing(s -> s.name());

}
[Student[name=coco, age=28], Student[name=lily, age=28], Student[name=jini, age=33], Student[name=sol, age=39]]
728x90
반응형