Comparable은 Comparable 인터페이스에 포함된 유일한 메소드입니다.
Object의 equals 메소드와 특성은 비슷하지만, 단순한 동치성 검사 이외에 순서 비교가 가능하며 좀 더 일반적입니다.
먼저 CompareTo의 규약을 살펴봅시다.
- 본 메소드는 이 객체와 인자로 주어진 객체를 비교한다. 이 객체의 값이 인자로 주어진 객체보다 작으면 음수를, 같으면 0을, 크면 양수를 반환한다. 인자로 전달된 객체의 자료형이 이 객체와 비교 불가능한 자료형인 경우에는 ClassCast Exception 예외를 던진다.
- 아래의 명세에 등장하는 sgn(expression)은 수학에서의 signum 함수를 나타내는 것으로 -1, 0, 1 가운데 한 값을 반환한다. 어떤 값이 반환될지는 expression의 값이 음수인지, 0인지, 양수인지에 따라서 결정된다.
1. compareTo를 구현할 때는 모든 x와 y에 대해 sgn(x.compareTo(y)) == -sgn(y.compareTo.(x))가 만족되도록 해야 한다. y.compareTo(x) 가 예외를 발생시킨다면 x.compareTo(y) 도 그래야 하고, 그 역도 성립해야 한다.
2. compareTo를 구현할 때는 추이성이 만족되돍 해야한다. 즉, x.compareTo(y) > 0 && y.compareTo(z) 이면, x.compareTo(z) > 0 이어야 한다.
3. 마지막으로 x.compareTo(y) == 0 이면 sgn(x.compareTo(z)) == sgn(y.compareTo(z))의 관계가 모든 z에 대해 성립하도록 해야 한다.
4. 강력히 추천하지만 절대적으로 요구되는 것이 아닌 조건 하나는 x.compareTo(y) == 0 == (x.equals(y))이다. 일반적으로 Comparable 인터페이스를 구현하면서 이 조건을 만족하지 않는 클래스는 반드시 그 사실을 명시해야한다. 이렇게 적을 것을 추천한다 "주의: 이 클래스의 객체들은 equals에 부합하지 않는 자연적 순서를 따른다."
한번 살펴보죠.
1번은 방향이 달라도 관계는 유지되어야 한다는 이야기 입니다.
예를 들어 sgn(1.compareTo(2)) 는 값이 -1일 겁니다. 이를 뒤집으면 1이겠죠? ( 2.compareTo(1) = 2 - 1)
거기에 마이너스(-)가 붙으면 같은 값이 됩니다. 이처럼 방향을 뒤집어도 객체간 대소관계는 유지되어야 된다는 명세입니다.
2번은 추이성에 대한 명세입니다. equals 에 대해서 다룰 때 x < y 이고 y < z 라면 z는 x보다 커야한다는 것을 다루었습니다.
(http://meaownworld.tistory.com/80?category=710953)
이처럼 compareTo를 오버라이드 할 때에는 같은 명세를 지켜줘야 합니다.
3번은 반사성에 관한 이야기입니다. x.compareTo(y) == 0 이라는 이야기는 x와 y가 같은 숫자라는 이야기인데, 그렇다면
sgn(x.compareTo(z)) == sgn(y.compareTo(z)) 관계의 z값이 어떤값이라도 항상 같은 공식이 유지되어야 한다는 것입니다.
4번은 규악이라기 보다는 권고사항입니다. compareTo과 equals 의 결과가 같지 않다면, 컬렉션 인터페이스(Collection, Set, Map)의 일반규약을 위반할 수 있습니다. 이런 인터페이스의 일반규약이 equals 메소드를 기반으로 작성되었지만, 실제 동치성은 equals 대신 compareTo를 통해 검사하기 때문입니다.
이제 제가 짜본 예제코드를 한번 봅시다.
import java.util.ArrayList;
import java.util.Collections;
public class Point implements Comparable<Point>{
int xnum=0;
// 생성자
public Point(int xnum) {
this.xnum = xnum;
}
@Override
public int compareTo(Point o) {
if(xnum>o.xnum) return 1;
else if(xnum==o.xnum) return 0;
return -1;
}
public static void main(String[] args){
Point p1 = new Point(1);
Point p2 = new Point(2);
Point p3 = new Point(3);
Point p4 = new Point(4);
ArrayList<Point> list = new ArrayList<Point>();
list.add(p3);
list.add(p2);
list.add(p1);
list.add(p4);
for (Point point : list){
System.out.print(point.xnum +" ");
}
System.out.println("\n");
Collections.sort(list);
// 오름차순 정령 - 일반 배열의 경우 Arrays.sort(변수);
for (Point point : list){
System.out.print(point.xnum +" ");
}
System.out.println("\n");
}
}
간단하게 만든 클래스 안에 int 를 넣고 Compareble을 구현했음을 이용해서 정렬 메소드를 테스트 해본 것입니다.
보시면 list에 값을 넣을 때 3 2 1 4 처럼 주구난방 식으로 넣고 compareTo 를 오버라이드 했습니다.
compareTo 메소드의 필드 비교 방식은 동치성 검사라기 보다는 순서 비교의 성격을 띱니다.
이 때문에 main을 실행하면 객체 참조 필드는 compareTo 메소드를 재귀적으로 호출하여 비교해 정렬합니다.
일반 배열의 경우(int, long, double 등)은 Arrays.sort() 를 이용하시면 됩니다.
결과를 보시면 오름차순으로 정렬이 된 것을 볼 수 있습니다.
import java.util.Collections;
import java.util.Set;
import java.util.TreeSet;
public class WorldList {
public static void main(String[] args){
Set<String> s = new TreeSet<String>();
String[] a = {"f", "z", "z", "z", "a", "b", "j", "j", "q"};
Collections.addAll(s, a);
System.out.print(s);
}
}
String은 기본적으로 Comparable을 구현하고 있는 참조 클래스 입니다.
때문에 명령행 인자들을 알파벳 순서로 정렬하는 동시에 중복을 제거하고 있는 모습을 확인할 수 있습니다.
public int compareTo(PhoneNumber pn) {
// 전화번호 = 지역번호 - 국번 - 회선
// 지역 번호 비교
int areaCodeDiff = areaCode - pn.areaCode;
if (areaCodeDiff != 0) return areaCodeDiff;
// 국번 비교
int prefixDiff = prefix = pn.prefix;
return prefixDiff;
// 회선 비교
return lineNumber - pn.lineNumber;
}
이 코드는 이펙티브 자바에서 든 예입니다.
전화번호를 비교해서 앞자리 수가 크거나 작으면 그 값을 리턴합니다. 그것만으로 대소관계는 확인이 가능하니까요.
만약 0이라면 다음 자리수까지 확인을 하는 방식입니다.
이러한 방식을 사용할 때 주의할 점은 필드가 음수가 아니어야 한다는 점입니다.
만약 필드가 음수가 되면 integer.Max_Value 값을 넘어 32비트가 초과될수 있기 때문에 오버플로우가 발생합니다.
때문에 주의가 필요합니다.
'프로그래밍 > Java' 카테고리의 다른 글
자바 Exception 예외 (throwable, Error, Exception, RuntimeException) (0) | 2018.02.09 |
---|---|
이펙티브 자바 규칙 10 - toString은 항상 재정의하자 (0) | 2018.02.06 |
디자인 패턴 - 전략 패턴(Strategy pattern) 예시 / 예제 (0) | 2018.02.06 |