JAVA/Effective java
Comparable을 구현할지 고려하라
100win10
2021. 1. 21. 17:54
Comparable 인터페이스의 유일무이한 메서드인 compareTo를 알아보자.
- compareTo는 Object의 메서드가 아니다.
- compareTo는 Object의 equals와 비슷하지만 두 가지가 다르다.
- 단순 동치성 비교에 더해 순서까지 비교할 수 있으며 제네릭하다.
Arrays.sort(a); |
- Comparable을 구현했다는 것은 그 클래스의 인스턴스들에는 자연적인 순서가 있음을 뜻하고
- 따라서 Comparable을 구현한 객체들의 배열은 다음처럼 손쉽게 정렬할 수 있는 것이다.
- 검색, 극단값 계산, 자동 정렬 컬렉션 관리 역시 쉽게 할 수 있다.
- 다음 프로그램은 명령줄 인수들을 알파벳순으로 출력한다. String이 Comparable을 구현한 덕이다.
- 사실상 자바 플랫폼 라이브러리의 모든 값 클래스와 열거 타입이 Comparable을 구현했다.
- 알파벳, 숫자, 연대 같이 순서가 명확한 값 클래스를 작성한다면 반드시 Comparable 인터페이스를 구현하자.
compareTo 메서드의 일반 규약은 equals의 규약과 비슷하다. 이 객체와 주어진 객체의 순서를 비교한다. 이 객체가 주어진 객체보다 작으면 음의 정수를, 같으면 0을, 크면 양의 정수를 반환한다. 이 객체와 비교할 수 없는 타입의 객체가 주어지면 ClassCastException을 던진다. 다음 설명에서 sgn(표현식) 표기는 수학에서 말하는 부호 함수(signum function) 을 뜻하며, 표현식의 값이 음수,0,양수일 때 -1,0,1을 반환하도록 정의했다. Comparable을 구현한 클래스는 모든 x,y에 대해 sgn(x.compareTo(y)) == -sgn(y.compareTo(x))여야 한다. 따라서 x.compareTo(y)는 y.comopareTo(x)가 예외를 던질때에 한해 예외를 던져야 한다. Comparable을 구현한 클래스는 추이성을 보장해야 한다. 즉, (x.compareTo(y) > 0 && y.compareTo(z) > 0) 이면 x.compareTo(z) > 0 이다. Comparable을 구현한 클래스는 모든 z에 대해 x.compareTo(y) == 0 이면 sgn(x.compareTo(z)) == sgn(y.compareTo(z)) 이다. 이번 권고가 필수는 아니지만 꼭 지키는 게 좋다. (x.compareTo(y) == 0) == (x.equals(y))여야 한다. Comparable을 구현하고 이 권고를 지키지 않는 모든 클래스는 그 사실을 명시해야 한다. 다음과 같이 명시하면 적당할 것이다. "주의: 이 클래스의 순서는 equals메서드와 일관되지 않다" |
- 모든 객체애 대해 전역 동치관계를 부여하는 equals 메서드와 달리, compareTo는 타입이 다른 객체를 신경 쓰지 않아도 된다.
- 타입이 다른 객체가 주어지면 간단히 ClassCastException을 던져도 되며, 대부분 그렇게 한다.
- hashCode 규약을 지키지 못하면 해시를 사용하는 클래스와 어울리지 못하듯, compareTo 규약을 지키지 못하면 비교를 활용하는 클래스와 어울리지 못한다.
- 비교를 활용하는 클래스의 예로 정렬된 컬렉션인 TreeSet와 TreeMap, 검색과 정렬 알고리즘을 활용하는 유틸 클래스인 Collections와 Arrays가 있다..
- compareTo와 equals가 일관되지 않는 BigDecimal 클래스를 예로 생각해보자.
- 빈 HashSet 인스턴스를 생성한 다음 new BigDecimal("1.0")과 new BigDecimal("1.00")을 차례로 추가한다.
- 이 두 BigDecimal은 equals 메서드로 비교하면 서로 다르기 때문에 HashSet은 원소를 2개 갖게 된다.
- 하지만 HashSet 대신 TreeSet을 사용하면 원소를 하나만 갖게 된다.
- compareTo 메서드로 비교하면 두 BigDecimal 인스턴스가 똑같이 때문이다.
- Comparable은 타입을 인수로 받는 제네릭 인터페이스이므로 compareTo 메서드의 인수 타입은 컴파일 타임에 정해진다.
- 입력 인수의 타입을 확인하거나 형변환할 필요가 없다.
- 클래스에 핵심 필드가 여러 개라면 어느 것을 먼저 비교하느냐가 중요해진다.
- 가장 핵심적인 필드부터 비교해나가자.
- 비교 결과가 0이 아니라면, 즉 순서가 결정되면 거기서 끝이다. 그 결과를 곧장 반환하자.
- 핵심 필드가 똑같다면 같지 않은 필드를 찾을 때까지 그다음 중요한 필드를 비교해나간다.
- 자바 8에서는 Comparator 인터페이스가 일련의 비교자 생성 메서드와 팀을 꾸려 메서드 연쇄 방식으로 비교자를 생성할 수 있게 되었다.
- 이 비교자들을 Comparable 인터페이스가 원하는 compareTo 메서드를 구현하는데 활용할 수 있다.
- 이 방식들은 간결하지만 약간의 성능 저하가 뒤따른다.
핵심 정리
- 순서를 고려해야 하는 값 클래스를 작성한다면 꼭 Comparable 인터페이스를 구현하자.
- 해당 인스턴스들을 쉽게 정렬하고, 검색하고, 비교 기능을 제공하는 컬렉션과 어우러지도록 해야 한다.
- compareTo 메서드에서 필드의 값을 비교할 때 <와 > 연산자는 쓰지 말아야 한다.
- 그 대신 박싱된 기본 타입 클래스가 제공하는 정적 compare 메서드나 Comparator 인터페이스가 제공하는 비교자 생성 메서드를 사용하자.
참고 자료