Duplicate
🧭

Comparable, compareTo 사용법과 주의해야 할 점

태그
Java
Lambda
Effective Java
공개여부
작성일자
2021/02/01
Leecode 문제를 풀다가 Comparable 에 대한 정리가 필요한 순간이 왔다. Arrays.sort 를 사용하는데 어떻게 정렬하는지 조금 아리까리해서 이번 기회에 완전히 잡아야겠다는 생각이 들어 정리하게 되었다. 여기에 정리한 내용은 "Effective Java 14. Comparable을 구현할지 고민하라" 에서 필요한 부분만 발췌했다.
compareTo 는 Comparable 인터페이스에 유일하게 존재하는 method 이다.
interface Comaparable { int compareTo(T t); }
Java
String, Integer 와 같이 값을 갖는 모든 객체와 Enum 타입은 Comparable 을 구현했다.
Screen_Shot_2021-02-02_at_0.13.02.png
Integer 클래스에서 구현한 Comparable
compareTo 는 주어진 객체의 순서를 비교한다.
객체가 주어진 객체보다 작으면 음의 정수, 같으면 0, 크면 양의 정수를 반환한다. 따라서 다음의 test 가 통과한다 만약 비교할 수 없는 타입이 주어지면 ClassCastException 이 발생한다.
@Test @DisplayName(value = "compareTo 가 음수가 나오게 하자") void compareToMinus() { Integer a = 1_000; Integer b = 2_000; assertThat( // 대상객체 a는 1000이고 a.compareTo(b) // 주어진 객체 b는 2000이다. // 주어진 객체보다 작아 -1을 반환한다 ).isNegative(); assertThat( a.compareTo(b) ).isEqualTo(-1); }
Java
@Test @DisplayName(value = "compareTo 가 양수가 나오게 하자") void compareToPlus() { Integer a = 2_000; Integer b = 500; assertThat( // 대상객체는 2,000 이고 a.compareTo(b) // 주어진 객체는 500 이다. // 주어진 객체보다 크기 때문에 1을 반환한다 ).isPositive(); assertThat( a.compareTo(b) ).isEqualTo(1); }
Java
eqauls 와 다음과 같은 내용이 매우 유사하다
(다음의 sgn 은 표현식이며 부호 함수를 의미한다 표현식이 음수, 0, 양수일때 -1, 0, 1을 반환한다)
1.
sgn(x.compareTo(y)) == -sgn(y.compareTo(x)) 가 성립한다.
sgn(x.compareTo(y)) 이 exception을 발생한다면 sgn(y.compareTo(x)) 도 예외를 발생한다.
2.
추이성을 보장한다
x.compareTo(y) > 0 && y.compareTo(z) > 0 가 성립하면
x.compareTo(z) > 0 도 성립한다
3.
마찬가지로 x, y, z 에 대해
x.compareTo(y) == 0 이면
sgn(x.compareTo(z)) == sgn(y.compareTo(z)) 이 성립한다
4.
권고사항 (되도록 지키면 좋다)
(x.compareTo(y) == 0) == (x.equals(y)) 여야 한다.
이 코드를 지키지 못한다면 그 사실을 명시해야 한다.

compareTo 는 동치인지 비교하는게 아니라 그 순서를 비교한다.

만약 객체 참조의 필드를 비교하고자 한다면 재귀적으로 compareTo 를 호출해야 한다. 만약 Comparable 을 구현하지 않았다면 Compator 를 사용한다.
class PhoneNumber { public int compareTo(PhoneNumber pn) { int result = Short.comparable(areaCode, pn.areaCode); // 가장 먼저 비교할 필드 if (result == 0) { result = Short.compare(prefix, pn.prefix); // 두번째로 비교할 필드 if (result == 0) { result = Short.compare(lineNum, pn.lineNum); // 세번째로 비교할 필드 } } return result; } }
Java
이 코드가 Java8 에 와서 굉장히 간지나게 변했다.
class PhoneNumber { private static final Comparator<PhoneNumber> COMPARATOR = comparingInt((PhoneNumber pn) -> pn.areaCode) .thenComparingInt(pn -> pn.prefix) .thenComparingInt(pn -> pn.lineNum); public int compareTo(PhoneNumber pn) { return COMPARATOR.compare(this, pn); } }
Java
Java 의 type 추론 능력이 뛰어나지 않기 때문에 (PhoneNumber pn) 와 같이 명시할 필요가 있다.
첫 번째 비교인 comparingInt 가 진행되고 다음 비교 인자를 비교하기 위해 thenComparingInt 를 사용하여 그 다음 변수를 비교하고, 한번 더 그 다음 변수를 비교한다.
그런데 두개의 매개변수를 받아 정렬을 해야 하는 경우가 있다.
Arrays.sort(people, new Comparator<int[]>() { @Override public int compare(int[] o1, int[] o2) { return o1[0] == o2[0] ? o1[1] - o2[1] : o2[0] - o1[0]; } });
Java
Screen_Shot_2021-02-02_at_2.12.04.png
IntelliJ 에서 Lambda로 변경하라는 메세지가 떳다!!
intelliJ 에서 보면 Replace with lambda 메세지가 나온다.
Arrays.sort(people, (o1, o2) -> o1[0] == o2[0] ? o1[1] - o2[1] : o2[0] - o1[0]);
Java
람다를 이용하면 이렇게 한줄로 표현할 수 있다.
하지만, 이 방법은 사용하면 안된다. 위 코드는 leetcode 에서 나온 문제로 o1, o2 에 대한 범위가 명시되어 있으니 문제가 없지만 만약 값이 int 범위를 넘거나 매개변수가 int가 아닌 소수점이 들어온다면?
즉, 정수 overflow를 일으키거나, IEEE754 부동 소수점 계산에 따른 오류가 발생할 수 있다.
따라서 다음과 같이 수정하여 사용하는걸 권장한다
Arrays.sort(people, (o1, o2) -> Objects.equals(o1[0], o2[0]) ? Integer.compare(o1[1], o2[1]): Integer.compare(o2[0], o1[0]);
Java
Made with 💕 and Oopy