코드 한 줄의 기록

Java Comparable과 Comparator 완벽 가이드 - 정렬 전략 완전 정복 본문

JAVA

Java Comparable과 Comparator 완벽 가이드 - 정렬 전략 완전 정복

CodeByJin 2025. 10. 27. 08:46
반응형

안녕하세요! 오늘은 Java에서 정렬을 다룰 때 반드시 알아야 하는 Comparable과 Comparator에 대해 깊이 있게 알아보려고 합니다. 저도 처음에는 이 둘의 차이가 헷갈렸는데, 실무에서 직접 사용하면서 체득한 내용들을 공유하려고 해요.

Comparable vs Comparator, 뭐가 다를까?

처음 이 개념들을 접했을 때 "둘 다 정렬하는 건데 왜 두 개나 있지?"라는 생각이 들었어요. 근데 써보니까 각자 확실히 용도가 다르더라고요.
 
Comparable은 java.lang 패키지에 있어서 import도 필요 없고, 클래스 자체에 "나는 이렇게 비교되어야 해!"라는 기본 규칙을 심어주는 거예요. compareTo(T o) 메서드 하나만 구현하면 됩니다.
 
Comparator는 java.util 패키지에 있고, 클래스 밖에서 "이번엔 이 기준으로 정렬해볼까?"하고 상황에 따라 다양한 정렬 기준을 만들 수 있어요. compare(T o1, T o2) 메서드를 구현합니다.

비교 방식의 핵심 차이

Comparable은 자기 자신과 파라미터 객체를 비교합니다. 즉, this.value와 other.value를 비교하는 거죠. 반면 Comparator는 파라미터로 받은 두 객체를 비교합니다. 자기 자신과는 상관없이 o1과 o2를 비교하는 겁니다.
 
이게 실무에서 어떻게 쓰이냐면, 예를 들어 Student 클래스를 만들 때 기본적으로 학번으로 정렬되게 하려면 Comparable을 구현하고, 특정 상황에서만 성적순이나 이름순으로 정렬하고 싶을 때 Comparator를 따로 만드는 식이에요.

Comparable 제대로 사용하기

Comparable은 클래스에 "자연스러운 순서"를 정의할 때 사용합니다. Integer는 숫자 크기 순, String은 사전 순처럼 말이죠.

public class Student implements Comparable<Student> {
    private int studentId;
    private String name;
    private int score;
    
    public Student(int studentId, String name, int score) {
        this.studentId = studentId;
        this.name = name;
        this.score = score;
    }
    
    @Override
    public int compareTo(Student other) {
        // 학번 기준 오름차순 정렬
        return this.studentId - other.studentId;
    }
}

 
return 값의 의미를 정확히 알아야 합니다.

  • 양수 : 현재 객체(this)가 비교 대상(other)보다 크다
  • 0 : 두 객체가 같다
  • 음수 : 현재 객체(this)가 비교 대상(other)보다 작다
@Override
public int compareTo(Student other) {
    return Integer.compare(this.studentId, other.studentId);
}

Comparator의 강력한 활용법

public class StudentScoreComparator implements Comparator<Student> {
    @Override
    public int compare(Student s1, Student s2) {
        return Integer.compare(s2.getScore(), s1.getScore()); // 성적 내림차순
    }
}

람다 표현식으로 간결하게

List<Student> students = new ArrayList<>();
students.add(new Student(3, "김철수", 85));
students.add(new Student(1, "이영희", 92));
students.add(new Student(2, "박민수", 78));

// 성적 기준 내림차순 정렬
Collections.sort(students, (s1, s2) -> Integer.compare(s2.getScore(), s1.getScore()));

// 또는 더 간결하게
students.sort((s1, s2) -> s2.getScore() - s1.getScore());

Java 8의 편리한 메서드들

// 이름 기준 정렬
students.sort(Comparator.comparing(Student::getName));

// 성적 기준 정렬
students.sort(Comparator.comparing(Student::getScore));

// 성적 기준 역순 정렬
students.sort(Comparator.comparing(Student::getScore).reversed());

메서드 체이닝으로 다중 조건 정렬

students.sort(Comparator.comparingInt(Student::getScore)
                        .reversed()
                        .thenComparing(Student::getName));

null 처리도 깔끔하게

students.sort(Comparator.nullsFirst(Comparator.comparing(Student::getName)));
students.sort(Comparator.nullsLast(Comparator.comparing(Student::getScore)));

Arrays.sort()와 Collections.sort()의 내부 비밀

기본형 배열은 Dual-Pivot QuickSort, 객체 배열은 TimSort를 사용합니다. Collections.sort()는 내부적으로 Arrays.sort()를 호출합니다.

실무 팁: 어떤 걸 언제 써야 할까?

  1. 클래스에 명확한 기본 정렬 기준이 있다면 → Comparable 구현
  2. 여러 기준으로 정렬해야 한다면 → Comparator 사용
  3. 정렬 기준을 자주 바꾼다면 → 람다 활용
  4. 대용량 데이터라면 → TimSort가 안정적

성능 최적화 실전 팁

1. 비교 연산 최소화

students.forEach(Student::calculateTotalScore);
students.sort(Comparator.comparingDouble(Student::getTotalScore));

 
2. comparingInt/Long/Double 활용

students.sort(Comparator.comparingInt(Student::getScore));

실전 예제: 복잡한 정렬 케이스

Comparator<Product> recommendOrder =
    Comparator.comparingDouble(Product::getRating).reversed()
              .thenComparingInt(Product::getSalesCount).reversed()
              .thenComparing(Product::getRegisteredDate).reversed();

병렬 정렬도 가능하다고?

List<Student> sortedStudents = students.parallelStream()
    .sorted(Comparator.comparingInt(Student::getScore).reversed())
    .collect(Collectors.toList());

 

Comparable과 Comparator, 처음엔 헷갈리지만 익숙해지면 정말 강력한 도구예요. Java 8 이후의 람다와 메서드 레퍼런스를 함께 사용하면 코드가 간결하고 가독성도 좋아집니다.

  • Comparable: 클래스의 기본 정렬 기준
  • Comparator: 다양한 정렬 기준 설정
  • thenComparing(): 다중 정렬 조건 연결
  • nullsFirst/Last: null 안전 처리
  • comparingInt 등: 성능 최적화

이 글이 여러분의 코드 품질 향상에 도움이 되었으면 좋겠습니다. 같이 공부해요!

Java Iterator와 Fail-Fast, 컬렉션 수정 시 주의할 점들

자바로 개발하다 보면 리스트나 셋 같은 컬렉션을 순회하면서 요소를 추가하거나 삭제해야 할 때가 있다. 그런데 이 과정에서 ConcurrentModificationException이라는 예외를 만나본 적 있지 않은가? 처

byteandbit.tistory.com

반응형