-
이펙티브 자바 Item 14 - Comparable을 구현할지 고려하라언어/Effective Java 2024. 5. 29. 16:16
1. Comparable 인터페이스란?
- Comparable 인터페이스는 객체를 정렬하는데 사용되는 메서드인 compareTo를 정의하고 있다.
- Comparable 인터페이스를 구현한 클래스는 반드시 compareTo를 정의해야 한다.
1.1 Comparable 인터페이스 특징
- 자바에서 같은 타입의 인스턴스를 비교해야만 하는 클래스들은 모두 Comparable 인터페이스를 구현하고 있다.
- Boolean 타입을 제외한 래퍼 클래스와 알파벳, 연대같이 순서가 명확한 클래스들은 모두 정렬이 가능하다.
- 기본 정렬 순서는 작은 값에서 큰 값으로 정렬되는 오름차순이다.
1.2 Comparable 인터페이스 구현
- Comparable을 구현했다는 것은 그 클래스의 인스턴스들에는 자연적인 순서가 있음을 의미한다.
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); } }
2. Comparable 인터페이스의 compareTo메서드
compareTo는 해당 객체와 전달된 객체의 순서를 비교한다.
- compareTo는 Object의 equals와 두가지 차이점이 있다. compareTo는 equals와 달리 단순 동치성에 더해 순서까지 비교할 수 있으며, 제네릭하다.
- Comparable을 구현했다는 것은 그 클래스의 인스턴스에 자연적인 순서가 있음을 뜻한다. 이를 구현한 배열은 Arrays.sort()로 쉽게 정렬이 가능하다.
3. compareTo 메서드 일반규약
이 객체와 주어진 객체의 순서를 비교한다.
객체가 주어진 객체보다 작으면 음의 정수, 같으면 0, 크면 양의 정수를 반환한다.
비교할 수 없는 타입이 주어지면 ClassCastException을 던진다.
- 대칭성
- 두 객체 참조의 순서를 바꿔 비교해도 예상한 결과가 나와야한다.
- 추이성
- 첫번째가 두번쨰보다 크고 두번째가 세번째보다 크면 첫번째는 세번째보다 커야함
- 반사성
- 크기가 같은 객체끼리는 어떤 객체와 비교하더라도 항상 같다.
- equals
- compareTo 메서드로 수행한 동치테스트 결과가 equals와 같아야한다.
4. equals와 compareTo의 차이
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);
- HashSet과 TreeSet은 서로 다른 메서드로 객체의 동치성을 비교한다.
- HashSet은 equals 기반, TreeSet은 compareTo기반
5. CompareTo 메서드 작성 요령
- Comparable은 타입을 인수로 받는 제네릭 인터페이스이므로 compareTo의 인수타입은 컴파일시 정해짐 형변환할 필요가 없다.
- null을 인수로 넣으면 NullPointerException을 던져야한다.
- compareTo는 동치가 아닌 순서 비교다
- 객체 참조 필드를 비교하려면 compareTo메서드를 재귀적으로 호출한다.
- Comparable을 구현하지 않은 필드나 표준이 아닌 순서로 비교해야한다면 Comparator를 사용하자
compareTo 메서드에서는 관계연산자(<,>)를 사용하지 않는 것을 추천한다.
- 박싱된 기본 타입 클래들에 새로 추가된 정적 메서드 compare을 대신 이용한다.
- 관계연산자는 오류를 유발할 가능성이 있기 때문이다.
클래스 핵심 필드 여러개 비교시
- 핵심필드가 여러개라면 가장 핵심적인 필드부터 비교하자
- 비교 결과가 0이 아니라면, 순서가 결정되면 바로 결과를 반환하면 된다. 똑같지 않은 필드를 찾을 때 까지 비교해나가도록 구현하면 된다.
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; }
6. Comparator 인터페이스
- 자바 8에서 Comparator 인터페이스가 일련의 비교자 생성 메서드와 메서드 연쇄방식으로 비교자를 생성할 수 있게 되었다.
- 비교자들은 compareTo 메서드를 구현하는데 활용될 수 있다. 이 방식은 간결하지만 약간의 성능 저하가 뒤따른다. 자바의 정적 임포트 기능을 활용하면 정적 비교자 생성 메서드들을 그 이름만으로 사용할 수 있어 코드가 훨씬 깔끔해진다.
- comparingInt, thenComparingInt등의 숫자용 기본타입을 커버하는 보조 생성 메서드들을 가지고 있다.
- comparing, thenComparing이란 객체 참조용 비교자 생성 메서드 또한 가지고 있다.
- 정적필드로 이를 가지고 다니면서 비교하자!
private static final Comparator<PhoneNumber> COMPARATOR = Comparator.comparingInt((PhoneNumber phoneNumber) -> phoneNumber.areaCode) .thenComparingInt(phoneNumber -> phoneNumber.prefix) .thenComparingInt(phoneNumber -> phoneNumber.lineNum); public int compareTo(PhoneNumber phoneNumber) { return COMPARATOR.compare(this, phoneNumber); }
해시코드 값을 사용할 때
static Comparator<Object> hashCodeOrder = new Comparator<Object>() { @Override public int compare(final Object o1, final Object o2) { return Integer.compare(o1.hashCode(), o2.hashCode()); } static Comparator<Object> hashCodeOrder = Comparator.comparingInt(o -> o.hashCode());
순서를 고려해야하는 값 클래스를 작성한다면 꼭 Comparable 인터페이스를 구현하여 그 인스턴스들을 쉽게 정렬하고, 검색하고, 비교 기능 제공하는 컬렉션과 어우러지도록 해야한다.
compareTo 메서드에서 필드 값을 비교할 때 <>연산자는 지양한다. 그 대신 박싱된 기본 타입 클래스가 제공하는 정적 compare메서드나 Comparator가 제공하는 비교자 생성 메서들을 이용하자
'언어 > Effective Java' 카테고리의 다른 글
이펙티브 자바 Item 16 - public 클래스에서는 public필드가 아닌 접근자 메서드를 사용하라 (0) 2024.05.30 이펙티브 자바 Item15 - 클래스와 멤버의 접근 권한을 최소화하라 (0) 2024.05.29 이펙티브 자바 Item 13 - clone 재정의는 주의해서 진행하라 (0) 2024.05.29 이펙티브 자바 Item 12 - toString은 항상 재정의하라 (0) 2024.05.29 이펙티브 자바 Itme 10 - equals는 일반 규약을 지켜 재정의 하라 (0) 2024.05.28