목요일, 3월 28
Shadow

#004 모든 객체에 공통적인 메소드 두번째

12 Comparable 인터페이스의 구현을 고려하자

compareTo 메소드는 Object 클래스에 정의되어 있지 않으며(자바 1.2에 추가됨), 대신에 Comparable 인터페이스에 유일하게 존재하는 메소드이다.
이 메소드는 Object.equals 메소드와 유사한 특성을 가지는데, 차이점이라면 두 객체가 동일한지 비교하는 것과 더불어 순서까지 비교할 수 있으며, 제네릭 타입을 지원한다.
클래스에서 Comparable 인터페이스를 구현하면, 이 인터페이스에 의존하는 수많은 알고리즘 및 컬렉션 클래스들과 상호연동이 가능하다.
실제로 자바 라이브러리의 모든 값은 Comparable 인터페이스를 구현하므로, 만일 알파벳 순, 숫자 순, 날짜 순과 같은 자연율을 갖는 값 클래스를 작성한다면 반드시 Comparable 인터페이스를 구현해야 한다.

compareTo 메소드의 보편적 계약


순서 판단을 위해 현재 객체(compareTo 메소드가 호출된)와 지정 객체(compareTo 메소드의 인자로 전달된)를 비교한다.
현재 객체의 값이 지정 객체보다 작으면 음수 정수값을, 같으면 0을, 크면 양수 정수값을 반환한다.
만일 지정 객체 타입이 현재 객체와 비교할 수 없는 타입이면 ClassCastException 예외를 발생시킨다.
다음 설명에서 sgn(표현식)은 signum 수학 함수를 나타내며, 표현식의 값이 음수면 -1을, 0이면 0을, 양수면 1을 반환한다.

대칭적: 모든 x, y에 대하여 sgn(x.compareTo(y)) == – sgn(y.compareTo(x))가 되도록 해야한다.
이행적: (x.compareTo(y) > 0 && y.compareTo(z) > 0)이면 x.compareTo(z) > 0 이어야 한다.
x.compareTo(y) == 0 이라면, 모든 z에 대해 sgn(x.compareTo(z)) == sgn(y.compareTo(z))이 되어야 한다.
반드시 요구되는 것은 아니지만, (x.compareTo(y)) == (x.equals(y)) 가 되도록 하는 것이 좋다.

Comparable 인터페이스를 구현하면서 이 조항을 지키지 않는 클래스에서는 API 문서에 “주의: 이 클래스는 equals 메소드와 다르게 자연율 순서를 지원한다.”라고 표시하기를 권한다.

번외 Comparable VS Comparator


책에 Comparator 에 대해 설명하지 않고 있기에 개인적으로 조금 검색을 해 보았습니다
프로그래밍에서의 인스턴스의 비교와 정렬은 매우 중요하다. 보통 잘 정돈된 언어라면 전부 정렬 함수를 제공하고 있다.

물론 우리의 Java도 그러한 방법을 제공하며, 두개의 인터페이스를 통해 정렬을 지정해줄 수 있다.
위에서 살펴보았듯 인스턴스 비교는 Comparable을 구현하는 방법도 있지만, Comparator 를 사용하는 방법도 있다.
Comparable 은 위에서 살펴봤듯이 객체 자체에 compareTo(T o) 를 구현하게 하여 정렬 방법을 값(객체) 자신이 알려주는 방식이다.
이걸 구현해두면 언젠가 자신을 사용할 객체가 Comparable 을 활용할 줄 아는 녀석이라면 그 객체에 의해 정렬이 되어진다

// Arrays.sort 메서드는 내부적으로 정렬 시 Comaprable을 사용할 줄 알기에 이러한 코드로 간단하게 정렬이 가능하다.
Arrays.sort(ComparableImpl);

Comparator 는 이와는 정렬에 대한 접근 방법이 다르다.
자신이 정렬의 대상이 되는게 아니며, 단지 정렬 방법에 대한 알고리즘만을 제공한다.
메서드 자체도 compare(T o1, T o2) 로 인자 두개를 받게 되어 있고, 두개의 동일 제네릭 타입 객체를 비교한다.
역시 이 인터페이스를 구현해두면 이 비교기를 사용할 수 있는 객체에 의해 정렬이 가능하다.

// Arrays.sort 메서드는 내부적으로 정렬 시 Comaprable 이외에도, Comparator를 정렬 시 빌려 사용할 줄 알기에 이러한 코드로 간단하게 정렬이 가능하다.
Arrays.sort(SomeImpl, ComparatorImpl);

이 둘의 차이는 자신이 좋아하는 사람에게

직접 러브레터를 전달하는 사람
친구를 통해 러브레터를 전달하는 사람

정도로 설명할 수 있겠다.
보통 직접 고백을 하는 경우가 성공율이 높듯이 Comparable 을 구현하는 방법이 더 일반적이고 많이 쓰인다.
그러나 예외도 있는 법. 어느때 Comparator 를 써야 할까.

1. 비교하고자 하는 인스턴스가 Comparable을 구현하지 않았을 경우
2. 보통의 정렬 방법과는 다른 정렬을 적용하고자 할 때. 예를 들어 가령 역순 정렬이라든지, 특정 값들은 뒤로 빼고 싶다든지 하는 자연율을 따르고 싶지 않을 때.

책에서 항상 강조하는 규칙 또한 Comaprable 과 동일하다.
재귀성 대칭성 이행성을 따라야 한다.
equals와의 관계도 완전히 똑같다. 그 결과가 equals와 반드시 같을 필요는 없지만 같게 해주는게 혼란을 피할 수 있다.

예를 들어보자.
만일 X, Y 인 객체가 있고 이 둘을 TreeSet 컬렉션에 넣는다고 가정한다. TreeSet의 생성자는 Comparator를 받을 수 있게 되어 있어서 내부적인 정렬에 대해 프로그래머가 간섭할 수 있다.
이때 구현하여 전달한 비교기의 구현과 객체의 관계가

X.equals(Y) == true 이지만 compare(X, Y) != 0 일 경우

TreeSet에 두 객체를 입력할 경우 실제 입력되는건 하나이다.
나머지 하나는 무시되는데 입력시에는 equals 규약을 따르기 때문이다. 하지만 입력 된 뒤의 내부적인 정렬 유지시에는 compare를 따르게 되며 ‘equals는 무시된다.

creative by javacafe

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

이 사이트는 스팸을 줄이는 아키스밋을 사용합니다. 댓글이 어떻게 처리되는지 알아보십시오.