Java Comparable vs Comparator 완벽 이해하기
Java에서 정렬(Sort)을 공부하면 반드시 등장하는 핵심 개념이 바로:
Comparable vs Comparator
입니다.
특히 실무에서는:
- 상품 가격 정렬
- 회원 이름 정렬
- 날짜 정렬
- TreeSet / TreeMap
- Stream sorted()
전부 Comparator와 연결됩니다.
초보 시절에는 보통:
Comparable = 기본 정렬
Comparator = 사용자 정의 정렬
정도로 외우고 넘어갑니다.
하지만 실제로는:
- 내부 정렬 알고리즘
- TreeMap 동작 원리
- 정렬 안정성
- 람다 정렬
- 다중 조건 정렬
까지 연결됩니다.
이번 글에서는:
- Comparable
- Comparator
- compareTo()
- compare()
- TreeSet 내부 구조
- 람다 기반 정렬
- 실무 정렬 전략
까지 깊게 정리해보겠습니다.
1. 왜 정렬 기준이 필요할까?
예:
List<Integer> list =
Arrays.asList(3, 1, 2);
정렬:
Collections.sort(list);
가능.
왜냐하면:
Integer는 비교 기준 존재
하기 때문.
2. 객체는 어떻게 정렬할까?
예:
class User {
String name;
int age;
}
현재 JVM은:
어떻게 비교해야 하는지 모름
3. 그래서 필요한 것
핵심:
비교 기준 정의
입니다.
4. Comparable이란?
Comparable 은:
객체 자신의 기본 정렬 기준 정의 인터페이스
입니다.
5. Comparable 구조
public interface Comparable<T> {
int compareTo(T o);
}
핵심 메서드:
compareTo()
6. compareTo 의미
현재 객체(this)와 상대 객체(o) 비교
입니다.
7. 반환 규칙⭐⭐⭐⭐⭐
| 반환값 | 의미 |
| 음수 | 현재 객체가 더 작음 |
| 0 | 동일 |
| 양수 | 현재 객체가 더 큼 |
8. 예시
//오름차순으로 정의하고 싶다면
@Override
public int compareTo(User o) {
return this.age - o.age;
}
//내림차순으로 정의하고 싶다면
@Override
public int compareTo(User o) {
return o.age - this.age;
}
사용:
//나이 오름차순으로 정의한 경우
this.compareTo(o)
User a = new User(20);
User b = new User(30);
a.compareTo(b);
정렬결과
20, 30
//나이 내림차순으로 정의한 경우
User a = new User(40);
User b = new User(30);
a.compareTo(b);
정렬결과
40, 30
//오름차순
@Override
public int compareTo(User o) {
return Integer.compare(this.age, o.age);
}
//내림차순
@Override
public int compareTo(User o) {
return Integer.compare(o.age, this.age);
}
"compareTo를 직접 구현하는 일"은 꽤 있지만, 실무에서는 Comparator.comparing() 스타일이 가장 많이 보인다
9. compareTo (Comparable) 사용 예시
public class User implements Comparable<User> {
private int age;
@Override
public int compareTo(User o) {
return Integer.compare(this.age, o.age);
}
}
//Comparable을 구현하면 컬렉션 내부 속성메서드를 사용할 수 있음
사용:
List<User> list = new ArrayList<>();
list.add(new User(30));
list.add(new User(10));
list.add(new User(20));
//정렬 가능
Collections.sort(users);
//또는
users.sort(null);
//정렬 과정에서 내부적으로 user1.compareTo(user2)...를 계속 호출
//정렬결과
20
30
40
자동으로:
compareTo()
호출.
10. Comparable 장점
| 장점 | 설명 |
| 기본 정렬 기준 제공 | O |
| 코드 단순 | O |
| Collection 정렬 자동 지원 | O |
이 방식은
TreeSet<User>
TreeMap<User, String>
Collections.sort()
Arrays.sort()
등에서 사용됨.
11. Comparable 단점
문제:
정렬 기준 하나만 강하게 고정
됩니다.
12. 예시 문제
public class User implements Comparable<User> {
private int age;
@Override
public int compareTo(User o) {
return Integer.compare(this.age, o.age);
}
}
//사용
Collections.sort(userList);
//또는
userList.sort(null);
현재:
나이순 정렬
만 구현.
그런데:
이름순 정렬
도 필요하면?
문제 발생.
13. 그래서 등장한 Comparator 실무에서는 훨씬 많이 사용
Comparator 는:
외부에서 비교 전략 정의하는 인터페이스
입니다.
14. Comparator 구조
public interface Comparator<T> {
int compare(T o1, T o2);
}
핵심:
compare()
15. compare 의미
두 객체 비교
입니다.
16. 예시
Comparator<User> ageComparator =
(u1, u2) -> u1.age - u2.age;
Comparator<User> ageComparator =
new Comparator<User>() {
@Override
public int compare(User u1, User u2) {
return u1.getAge() - u2.getAge();
}
};
인터페이스로는 객체 생성 불가함. 익명클래스로 바로 객체 생성하는건 가능.
컴파일러가 구현클래스를 내부적으로 생성해서 new ComparatorImpl()한것과 비슷함.
이렇게 익명클래스가 람다가 가능한 이유는
추상 메서드가 하나만 있기 때문. " 함수형 인터페이스(Function Interface)"
public interface Comparator<T> {
int compare(T o1, T o2);
}
대표 예시
Comparator
Runnable
Callable
Predicate
Function
Consumer
Supplier
(런 내용은 함수형프로그래밍을 다루는 글에서 확인할 수 있음)
17. 사용 예시
users.sort(ageComparator);
sort안에 넣으면
자바는 sort()메서드 시그니처를 봄.
실제로 List인터페이스에는
void sort(Comparator<? super E> c)
가 있음. 즉, users.sort(???) 안에 Comparator를 넣어야 함
그래서 ageComparator를 보고 자바가 Comparator 구현체라는 것을 추론함.
실제로는 아래와 동일
users.sort(
new Comparator<User>() {
@Override
public int compare(User u1, User u2) {
return u1.getAge() - u2.getAge();
}
}
);
Comparator<User> 객체생성 하나를 만들어서 넘기는데, 그 comp안에는
Comparator가 User 객체 두 개를 받아서 age를 비교해서 돌려주는 규칙( compare(User u1, User u2) )이 들어있음. 비교로직을 가진 객체.
sort에 전달.
users.sort(...) 를 컴파일러가 List<User>.sort(...)라고 이해함. List인터페이스 메서드 호출.
List 인터페이스에 void sort(Comparator<? super E> c) 가 있어서
E 는 List의 타입 파라미터. List<User>니까 E = User
void sort(Comparator<? super User> c)로 치환됨
users.sort(???) 에서 ???에는
Comparator<User> 또는 Comparator<Object> 같은 것이 올 수 있음 <? super T>
Comparator<User>는 인터페이스.
public interface Comparator<T> {
int compare(T o1, T o2);
}
//T에 User를 넣으면
public interface Comparator<User> {
int compare(User o1, User o2);
}
즉 Comparator<User>는 user 두 개를 비교할 수 있는 규칙을 의미
18. Comparator 장점⭐⭐⭐⭐⭐
| 장점 | 설명 |
| 다양한 정렬 기준 가능 | O |
| 외부 전략 분리 | O |
| 런타임 정렬 변경 가능 | O |
19. Comparable vs Comparator 핵심 차이
| 항목 | Comparable | Comparator |
| 위치 | 객체 내부 | 외부 |
| 메서드 | compareTo | compare |
| 목적 | 기본 정렬 | 추가 정렬 |
| 전략 수(정렬기준) | 보통 1개 | 여러 개 가능 |
20. 실무에서 Comparator 더 많이 쓰는 이유
실제 서비스에서는:
정렬 조건 매우 다양
하기 때문.
예:
- 가격순
- 이름순
- 최신순
- 인기순
public class User {
private String name;
private int age;
private LocalDate joinDate;
}
//나이순
users.sort(
Comparator.comparing(User::getAge)
);
//이름순
users.sort(
Comparator.comparing(User::getName)
);
//가입일순
users.sort(
Comparator.comparing(User::getJoinDate)
);
//나이 내림차순
users.sort(
Comparator.comparing(User::getAge)
.reversed()
);
//여러가지 정렬을 함께 하는 경우 (1차 age기준 오름차순, 2차 name기준 오름차순)
users.sort(
Comparator.comparingInt(User::getAge)
.thenComparing(User::getName)
);
* 람다식
User::getAge 를 넣으면
컴파일러가
user -> user.getAge()로 바꿔서 사용.
람다를 더 줄인 느낌
Comparator<User> comp =
(u1, u2) ->
Integer.compare(
u1.getAge(),
u2.getAge()
);
//거의 같은 느낌
Comparator<User> comp =
Comparator.comparingInt(
User::getAge
);
21. Java 8 이후 람다 등장
매우 중요.
예전:
new Comparator<User>() {
@Override
public int compare(User o1, User o2) {
return o1.age - o2.age;
}
}
//
Collections.sort(users, new Comparator<User>() {
@Override
public int compare(User o1, User o2) {
return o1.getAge() - o2.getAge();
}
});
22. 현대 Java 방식
// 1. Comparator 인터페이스 구현한 것. compare(u1, u2)의 결과를 직접 계산.
(users1, users2)
-> users1.age - users2.age
// 설명: int범위 초과 시 오버플로우. Comparator.comparing(User::getAge)은 int -> Integer 박싱 발생
// 2. Java가 내부적으로 Comparator를 만들어 줌.
users.sort(
Comparator.comparingInt(User::getAge)
);
//comparingInt() - Overflow 방지해서 안전, 성능도 조금 더 좋음 (오토박싱 없음. int그대로 사용. 객체생성 없음). '기준정렬Age' 가독성좋음
훨씬 간단.
Comparator.comparingInt도 Comparator생성기.
내부 개념은 아래와 거의 같음
Comparator<User> comp =
(u1, u2) ->
Integer.compare(
u1.getAge(),
u2.getAge()
);
반환 타입은 Comparator<User>
23. Comparator.comparing()
실무에서 매우 많이 사용.
users.sort(
Comparator.comparing(User::getAge)
);
24. Method Reference 사용
User::getAge
는 람다 축약 문법
25. 내림차순 정렬
Comparator.comparing(User::getAge)
.reversed()
26. 다중 조건 정렬
매우 중요.
예:
Comparator.comparing(User::getAge)
.thenComparing(User::getName)
의미:
나이 우선
같으면 이름순
27. TreeSet과 Comparable
TreeSet 은 내부적으로:
정렬 기준 필요
합니다.
28. TreeMap도 동일
TreeMap 역시:
compare 기반 정렬
사용.
29. compareTo 잘못 구현 시 문제
매우 중요.
예:
return 1;
만 하면?
30. 문제 발생 가능
대표:
- 정렬 깨짐
- TreeSet 중복 이상
- TreeMap 저장 오류
31. compareTo와 equals 관계
매우 중요.
Java 권장:
compareTo == 0 이면
equals도 true 권장
32. 왜 중요할까?
TreeSet은:
compareTo == 0
이면:
중복 객체 판단
가능.
33. 실무 버그 사례
예:
equals는 다른데
compareTo는 0
이면:
데이터 사라진 것처럼 보일 수 있음
34. String 비교도 Comparable
String 은:
Comparable<String>
구현 중.
즉:
Collections.sort(strings);
가능.
35. 내부 정렬 알고리즘
Java는 내부적으로:
- TimSort
- MergeSort 계열
등 사용.
36. 정렬 안정성(Stable Sort)
Java 정렬은 대부분:
Stable Sort
입니다.
즉:
동일 값의 기존 순서 유지
가능.
37. Comparator 실무 패턴⭐⭐⭐⭐⭐
null 처리
Comparator.nullsLast(...)
문자열 대소문자 무시
String.CASE_INSENSITIVE_ORDER
숫자 역순
Comparator.reverseOrder()
38. Stream 정렬
Java 8 이후 매우 많이 사용.
users.stream()
.sorted(
Comparator.comparing(User::getAge)
)
39. 성능 관점
정렬은 기본적으로:
O(N logN)
입니다.
대용량 데이터는 비용 큼.
40. DB 정렬 vs Java 정렬
실무에서 매우 중요. DB 정렬을 훨씬 많이 사용함.
대량 데이터는 보통:
100만건을 조회 후 Java에서 정렬하기 보다 DB에서 ORDER BY 해서 20건만 가져오는 식으로 설계
//SQL
DB ORDER BY 우선
//Java(JPA)
PageRequest.of(
0,
20,
Sort.by("age").ascending()
);
권장.
메모리 정렬 비용 큼.
직접 사용할 일은 많지 않아도 compareTo를 알야 함
TreeSet<User>
treeSet.add(user); 가 내부적으로 user.compareTo(otherUser)를 계속 호출함
PriorityQueue<User> (우선순위큐)도 compareTo() 또는 Comparator를 사용
compareTo의 반환 규칙(음수/0/양수)은 Comparator도 동일하게 사용하므로 반드시 이해하고 있어야 함.
41. 실무에서 자주 하는 실수
1) compareTo 계약 위반
TreeSet 동작 이상 가능.
2) subtraction 비교 오버플로우
안 좋은 예:
return a - b;
큰 숫자 위험.
실무에선:
Integer.compare(a, b)
추천.
3) equals와 compareTo 불일치
데이터 유실처럼 보일 수 있음.
42. 핵심 흐름 요약
Comparable
= 객체 기본 정렬
Comparator
= 외부 정렬 전략
43. 가장 중요한 핵심 한 줄
Comparable은 “객체 자체의 기본 정렬 기준”,
Comparator는 “외부에서 주입하는 정렬 전략”이다
입니다.
44. 정리
Comparable vs Comparator는 단순 정렬 문법이 아닙니다.
실제로는:
- TreeMap/TreeSet 동작
- Collection 정렬 전략
- Stream API
- 람다 표현식
- 객체 비교 계약
전체와 연결되는 Java 핵심 개념입니다.
특히 실무에서는:
- Comparator.comparing()
- thenComparing()
- compareTo 계약
- 정렬 안정성
- DB vs 메모리 정렬
을 정확히 이해하는 것이 매우 중요합니다.
다음 글에서는:
Iterator와 Iterable
을 foreach 내부 동작, fail-fast, ConcurrentModificationException, Stream과 차이까지 포함해서 깊게 정리해보겠습니다.
'language > java' 카테고리의 다른 글
| Java Generic(제네릭) 기초 완벽 이해하기 (0) | 2026.05.27 |
|---|---|
| Java Iterator와 Iterable 완벽 이해하기 (0) | 2026.05.27 |
| Java equals()와 hashCode() 완벽 이해하기 (0) | 2026.05.27 |
| Java Hash 충돌(Hash Collision) 처리 방식 완벽 이해하기 (0) | 2026.05.27 |
| Java HashMap 내부 구조 완벽 이해하기 (0) | 2026.05.27 |
댓글