(이펙티브 자바) 아이템 14. Comparable을 구현할지 고려하라.
들어가며
회원정보를 담는 DTO인 Member 클래스를 생성했다. 성이 '김'씨인 회원을 조회하는 비즈니스 로직이 필요하여 SQL(혹은 JPQL)을 통해 조회한다. 원하는 결과를 얻었지만 기준 없는 조회 결과가 마음에 들지 않는다. 클라이언트가 보기 편하게 이름을 오름차순으로 정렬해야 할 것 같다. Order by 구문을 통해 정렬을 추가하니 비로소 마음에 든다.
실무에서 흔히 발생할 수 있는 상황이다. 요즘에는 데이터베이스 없이 개발하는 경우는 거의 없기 때문에 고가용성을 요구하는 비즈니스 로직을 쿼리문으로 해결하는 경우가 많다. (이 방법의 효용에 대해서는 이번 주제와는 어긋나기에 논하지 않겠다) 반대로 말하자면 데이터베이스를 사용하지 않는 애플리케이션은 자바로 조회, 그룹핑, 정렬 등을 직접 구현해줘야 한다.
Comparable 인터페이스는 무엇인가?
이 글은 '데이터베이스 없이 자바 내에서 정렬을 어떻게 구현할까?'란 주제를 중심으로 글을 설명할 것이다. java.lang.Comparable 인터페이스는 아래와 같이 compareTo(T o) 메서드 하나만 가지고 있다.
public interface Comparable<T> {
public int compareTo(T o);
}
갑자기 영문법 시간이 되는 것 같아 쓰면서도 이상하지만 클래스나 메서드를 이해하는데에는 필수다 보니 이해를 바란다. 'A compare to B'는 'A와 B를 비교한다'로 번역된다. 따라서 compareTo(T o)는 자기자신(this)와 파라미터(T o)를 비교하는 메서드로 이해할 수 있다. 무언가를 비교하면 결과는 세 가지로 정의된다. A < B, A = B, A > B 이 외의 결과는 논리적으로 나올 수 없다.
그래서 compareTo 메서드 역시 반환 타입이 int 지만 크게 세 가지로 정의할 수 있다. 1
" 음의 정수, 0, 양의 정수 "
기준이 헷갈릴 수 있다. 처음 볼 때 낯선 건 당연하다. 그래서 쉽게 비교할 수 있게 표를 준비했다. 항상 비교의 중심이 되는 것은 자기 자신(this) 객체가 된다.
A < B | A = B | A > B | |
A | A.compareTo(B) => -1 | A.compareTo(B) => 0 | A.compareTo(B) => 1 |
B | B.compareTo(A) => 1 | B.compareTo(A) => 0 | B.compareTo(A) => -1 |
샘플 코드를 작성해보자
먼저 머리말에서 이야기했던 Member 클래스를 만들어보자. 학습 테스트를 위해 간단히 회원 ID를 기준으로 내림차순 정렬하는 기능만 구현할 것이다.
public class Member implements Comparable<Member> {
private final Long id;
private final String name;
private final Long age;
public Member(Long id, String name, Long age) {
this.id = id;
this.name = name;
this.age = age;
}
@Override
public int compareTo(Member o) {
return orderById(o);
}
private int orderById(Member o) {
Long memberId = this.getId();
Long targetId = o.getId();
return memberId > targetId ? 1 : ((memberId == targetId) ? 0 : -1);
}
private int orderByName(Member o) {
String memberName = this.getName();
String targetName = o.getName();
return memberName.charAt(0) > targetName.charAt(0) ? 1 : ((memberName.charAt(0) == targetName.charAt(0)) ? 0 : -1);
}
private int orderByAge(Member o) {
Long memberAge = this.getAge();
Long targetAge = o.getAge();
return memberAge > targetAge ? 1 : ((memberAge == targetAge) ? 0 : -1);
}
// ... getter 코드
}
Member 클래스의 compareTo() 메서드를 보면 ID를 기준으로 순서를 정하고 있다. 이번에는 compareTo()를 활용하여 정렬하는 방법을 살펴보자.
public class AppRunner {
public static void main(String[] args) {
List<Member> members = new ArrayList<>();
members.add(new Member(2L, "imesung", 30L));
members.add(new Member(4L, "ssinsa", 28L));
members.add(new Member(1L, "bbubbush", 31L));
members.add(new Member(3L, "junu", 30L));
System.out.println("[Before Sort]");
members.stream().forEach((member -> System.out.println(member.toString())));
Collections.sort(members); // 정렬
System.out.println("[After Sort]");
members.stream().forEach((member -> System.out.println(member.toString())));
}
}
members객체에 무작위로 Member 객체를 추가했다. 정렬하기 전에 출력하면 당연히 등록한 순서대로 객체를 보여준다. 그렇다면 정렬한 후에는 내림차순이 되어 우리가 기대하는 것처럼 회원 ID가 4L인 'ssinsa' 객체가 먼저 출력될까?
[Before Sort]
Member{id=2, name='imesung', age=30}
Member{id=4, name='ssinsa', age=28}
Member{id=1, name='bbubbush', age=31}
Member{id=3, name='junu', age=30}
[After Sort]
Member{id=1, name='bbubbush', age=31}
Member{id=2, name='imesung', age=30}
Member{id=3, name='junu', age=30}
Member{id=4, name='ssinsa', age=28}
실제로는 오름차순 정렬이 되어 가장 마지막에 'ssinsa' 객체가 출력된다. 기본적으로 sort는 오름차순으로 정렬하기 때문이다. 내림차순으로 정렬하기 위해서는 아래와 같이 두 번째 파라미터로 Collections.reverseOrder() 메서드를 추가해주면 된다.
public class AppRunner {
public static void main(String[] args) {
List<Member> members = new ArrayList<>();
members.add(new Member(2L, "imesung", 30L));
members.add(new Member(4L, "ssinsa", 28L));
members.add(new Member(1L, "bbubbush", 31L));
members.add(new Member(3L, "junu", 30L));
System.out.println("[Before Sort]");
members.stream().forEach((member -> System.out.println(member.toString())));
Collections.sort(members, Collections.reverseOrder()); // 내림차순 정렬
System.out.println("[After Sort]");
members.stream().forEach((member -> System.out.println(member.toString())));
}
}
마치며
Comparable 인터페이스에 대해 간략히 살펴보았다. 책에서는 compareTo() 메서드의 일반 규약을 몇 가지 소개하지만 이를 설명하면 딱딱한 글이 될 것 같았다. 그래서 개념과 실제 사용 코드를 중심으로 부드럽게 풀어서 설명하는 것을 중심으로 글을 쓰도록 노력했다.
비교의 기준은 항상 자기 자신이며, 비교대상이 되는 객체보다 자신의 우선순위가 높다면(A > B) 양의 정수를, 낮다면(A < B) 음의 정수를, 같다면 0을 compareTo()의 반환 값으로 사용한다는 것만 확실히 안다면 이번 아이템의 학습목표는 이룬 셈이다.
![]() |
|
- compareTo() 메서드의 doc에는 사용방법이 자세히 기록되어 있다. 그중 @return 부분만 발췌하면 다음과 같다.
return a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object. [본문으로]