3주차 Unit 6.2 — Comparable<T> 의 자연 순서

Psj·2026년 5월 19일

F-lab

목록 보기
98/237

Unit 6.2 — Comparable<T> 의 자연 순서

F-LAB JAVA · 3주차 · Phase 6 · 객체 비교


📌 학습 목표

이 Unit을 끝내면 다음을 답할 수 있어야 한다.

  • Comparable<T> 인터페이스 의 정확한 정의는?
  • compareTo 메서드 의 반환값 의미는?
  • 자연 순서 (Natural Ordering) 가 무엇인가?
  • compareTo 의 5가지 계약 (반사성, 대칭성, 추이성 등) 은?
  • equalscompareTo 의 일관성 의 의미와 중요성은?
  • Integer.compare() 같은 정적 메서드를 쓰는 이유는?
  • 다중 필드 비교 의 우아한 작성 방법은?
  • TreeMap, TreeSet, Collections.sort 와 Comparable 의 관계는?
  • 자바 표준의 Comparable 구현 (Integer, String, LocalDate) 분석은?

🎯 핵심 한 문장

Comparable<T> 는 "자기 자신을 어떻게 비교하는가" 를 정의하는 인터페이스다.
compareTo 메서드가 음수 (this < other), 0 (같음), 양수 (this > other) 를 반환하며,
이로 인해 자연 순서 (Natural Ordering) 가 정의된다.
Collections.sort(list), TreeMap, TreeSet 이 이 순서를 활용해 자동 정렬.
equalscompareTo 의 일관성 을 지키지 않으면 TreeSet 의 contains 와 HashSet 의 contains 가 다른 결과를 반환.

비유 — 학생의 자기 순위

Comparable = 학생 스스로 결정한 순위 기준

Student implements Comparable<Student> {
    compareTo(other):
        - 내 점수가 높으면 양수 (나는 더 위)
        - 내 점수가 낮으면 음수 (나는 더 아래)
        - 같으면 0

이로 인해:
  - 학생들을 한 줄로 세울 수 있음 (자연 순서)
  - Collections.sort 가 자동으로 정렬
  - TreeMap 이 학생을 키로 자동 정렬 보관

→ Comparable = 자기 정렬 기준 + 자연 순서.


🧭 9개 섹션 로드맵

1. Comparable<T> 인터페이스의 정의
2. compareTo 의 반환값
3. 자연 순서 (Natural Ordering)
4. compareTo 의 5가지 계약
5. equals 와 compareTo 의 일관성
6. 다중 필드 비교
7. TreeMap, TreeSet, Collections.sort 의 활용
8. 자바 표준의 Comparable 분석
9. 면접 + 자기 점검

1️⃣ Comparable 인터페이스의 정의

1.1 인터페이스 정의

package java.lang;

public interface Comparable<T> {
    
    int compareTo(T other);
}

핵심:

  • java.lang 패키지 (기본 import)
  • 단 하나의 메서드compareTo
  • 제네릭 — 자기 타입 매개변수 (보통 자기 자신)
  • 함수형 인터페이스 는 아님 (관례적으로 lambda 부적합)

1.2 기본 구현 예

public class Person implements Comparable<Person> {
    private String name;
    private int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    @Override
    public int compareTo(Person other) {
        return Integer.compare(this.age, other.age);
        // 나이 오름차순
    }
}

// 사용
Person p1 = new Person("Alice", 25);
Person p2 = new Person("Bob", 30);
Person p3 = new Person("Charlie", 25);

p1.compareTo(p2);   // 음수 (25 < 30)
p2.compareTo(p1);   // 양수 (30 > 25)
p1.compareTo(p3);   // 0 (25 == 25)

1.3 자기 타입 매개변수

// 권장: Comparable<자기 자신>
public class Person implements Comparable<Person> {
    @Override
    public int compareTo(Person other) { ... }
}

// 부모 타입과 비교 (드물게)
public class Child extends Parent implements Comparable<Parent> {
    @Override
    public int compareTo(Parent other) { ... }
    // Child 는 Comparable<Parent>
}

// 제네릭 사용
public class Pair<T extends Comparable<T>> implements Comparable<Pair<T>> {
    private T value;
    
    @Override
    public int compareTo(Pair<T> other) {
        return value.compareTo(other.value);
    }
}

1.4 자바 표준의 Comparable 구현

// 모든 숫자 타입
public final class Integer implements Comparable<Integer> { ... }
public final class Long implements Comparable<Long> { ... }
public final class Double implements Comparable<Double> { ... }

// 문자열
public final class String implements Comparable<String> { ... }

// 날짜/시간
public final class LocalDate implements Comparable<ChronoLocalDate> { ... }
public final class LocalDateTime implements Comparable<ChronoLocalDateTime<?>> { ... }

// 자료형
public final class BigDecimal extends Number implements Comparable<BigDecimal> { ... }
public final class BigInteger extends Number implements Comparable<BigInteger> { ... }

// Enum
public abstract class Enum<E extends Enum<E>> implements Comparable<E> {
    public final int compareTo(E other) {
        return this.ordinal() - other.ordinal();
        // 선언 순서 기반
    }
}

1.5 Comparable 의 활용

List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6);

// Collections.sort — Comparable 자동 활용
Collections.sort(numbers);   // 자연 순서 정렬
// [1, 1, 2, 3, 4, 5, 6, 9]

// List.sort + null 비교기
numbers.sort(null);   // null = 자연 순서

// TreeMap, TreeSet — 자동 정렬
TreeSet<String> sortedNames = new TreeSet<>();
sortedNames.add("Charlie");
sortedNames.add("Alice");
sortedNames.add("Bob");
// 내부: [Alice, Bob, Charlie]

// Collections.max/min
Integer max = Collections.max(numbers);   // 9
Integer min = Collections.min(numbers);   // 1

1.6 Comparable 자체는 인스턴스화 불가

// Comparable 은 인터페이스
Comparable<Integer> c = ...;   // 어떻게 만드나?

// 직접 인스턴스화 불가
// new Comparable<Integer>() { ... };   // 익명 클래스 가능

// 보통:
// 1. 클래스가 implements
public class MyClass implements Comparable<MyClass> { ... }

// 2. Comparable 의 자식을 매개변수로
public <T extends Comparable<T>> T max(List<T> list) { ... }

// 3. 와일드카드
public <T extends Comparable<? super T>> void sort(List<T> list) { ... }

1.7 자기 점검 답변

Comparable 의 본질과 정의는?

:

  • 본질:

    • "자기 자신을 어떻게 비교하는가"
    • 자연 순서 정의
    • 자기 정렬 기준
  • 정의:

    • java.lang 패키지
    • 단일 메서드 compareTo(T)
    • 제네릭 (보통 자기 자신)
  • 구현체:

    • Integer, Long, Double 등 숫자
    • String, LocalDate 등
    • 사용자 클래스도 가능

→ Java 의 기본 비교 메커니즘.


2️⃣ compareTo 의 반환값

2.1 세 가지 반환값

compareTo(other) 의 반환값:

- **음수** (보통 -1): this < other (this 가 더 작음)
- **0**: this == other (같음)
- **양수** (보통 1): this > other (this 가 더 큼)

정확한 수치는 무관, 부호만 중요.

2.2 기본 예시

Integer a = 5;
Integer b = 10;

a.compareTo(b);   // 음수 (-5 또는 -1 등) — a < b
b.compareTo(a);   // 양수 (5 또는 1 등) — b > a
a.compareTo(5);   // 0 — 같음

// String 의 compareTo
String s1 = "apple";
String s2 = "banana";

s1.compareTo(s2);   // 음수 — apple 이 사전순 먼저
s2.compareTo(s1);   // 양수

2.3 음수/양수 의 정확한 값

// 자바 표준은 정확한 수치 명시 안 함
// 예: Integer.compareTo

public int compareTo(Integer other) {
    return (this.value < other.value) ? -1 : ((this.value == other.value) ? 0 : 1);
    // 또는 더 자주:
    return Integer.compare(this.value, other.value);
    // Integer.compare 는 -1, 0, 1 반환
}

// String.compareTo
public int compareTo(String other) {
    // 문자 코드 차이 반환
    // 예: "a".compareTo("b") = 'a' - 'b' = -1
    // 또는 길이 차이
}

// LocalDate.compareTo
public int compareTo(ChronoLocalDate other) {
    int cmp = (int)(toEpochDay() - other.toEpochDay());
    // 일 수 차이
}

2.4 부호만 중요한 이유

// 다음 모든 표현이 같은 의미:
// "a 가 b 보다 작다"

a.compareTo(b) < 0;
a.compareTo(b) == -1;     // 너무 엄격
a.compareTo(b) <= -1;
Integer.signum(a.compareTo(b)) == -1;   // 부호만

// 권장: < 0, == 0, > 0
if (a.compareTo(b) < 0) {
    // a 가 작음
}

if (a.compareTo(b) == 0) {
    // 같음
}

if (a.compareTo(b) > 0) {
    // a 가 큼
}

2.5 흔한 실수 — 직접 뺄셈

// ❌ 잘못 — 오버플로우 가능
@Override
public int compareTo(Person other) {
    return this.age - other.age;   // 위험!
}

// 예: this.age = Integer.MAX_VALUE, other.age = -1
// 결과: MAX_VALUE - (-1) = MAX_VALUE + 1 = MIN_VALUE (음수!)
// 의도와 반대

// ✓ 권장: Integer.compare
@Override
public int compareTo(Person other) {
    return Integer.compare(this.age, other.age);
}

// 내부 구현 (안전):
public static int compare(int x, int y) {
    return (x < y) ? -1 : ((x == y) ? 0 : 1);
}

2.6 정적 compare 메서드 활용

// Java 7+ 의 정적 compare 메서드들
Integer.compare(a, b);     // int 비교
Long.compare(a, b);         // long 비교
Double.compare(a, b);       // double 비교 (NaN 처리)
Boolean.compare(a, b);      // boolean 비교
Character.compare(a, b);   // char 비교

// 객체 비교
Objects.compare(a, b, Comparator.naturalOrder());

// 활용
@Override
public int compareTo(Person other) {
    return Integer.compare(this.age, other.age);
}

@Override
public int compareTo(Product other) {
    return Double.compare(this.price, other.price);
}

@Override
public int compareTo(Date other) {
    return Long.compare(this.timestamp, other.timestamp);
}

2.7 BigDecimal 의 compareTo vs equals

BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("1.00");

a.compareTo(b);   // 0 (같음, 값으로)
a.equals(b);      // false! (scale 다름)

// BigDecimal 의 equals 는 scale 도 비교
// compareTo 는 값만 비교

→ BigDecimal 사용 시 비교 방법 주의.

2.8 자기 점검 답변

compareTo 의 반환값과 안전한 비교 방법은?

:

  • 반환값:

    • 음수: this < other
    • 0: this == other
    • 양수: this > other
    • 정확한 수치 무관, 부호만 중요
  • 흔한 실수:

    • 직접 뺄셈 a - b → 오버플로우 위험
    • 예: MAX_VALUE - (-1) = MIN_VALUE
  • 권장:

    • Integer.compare(a, b) 등 정적 메서드
    • 자바 표준의 안전한 구현
    • 오버플로우 처리

3️⃣ 자연 순서 (Natural Ordering)

3.1 자연 순서의 정의

자연 순서 (Natural Ordering):

  Comparable 의 compareTo 가 정의하는 순서.

특징:
  - 클래스에 내재된 순서
  - 외부 Comparator 불필요
  - Collections.sort(list) 가 자동 활용
  - TreeMap, TreeSet 의 기본 순서

3.2 자바 표준의 자연 순서

Integer, Long: 숫자 오름차순
  Integer.compare(a, b);

String: 사전순 (lexicographic)
  - Unicode 값 기반
  - 대문자 < 소문자 (Z < a)
  
LocalDate: 시간 오름차순
  - 1900-01-01 < 2025-01-01

BigDecimal: 값 비교 (scale 무관)

Enum: 선언 순서 (ordinal)
  enum Status { DRAFT, ACTIVE, COMPLETED }
  // DRAFT < ACTIVE < COMPLETED

3.3 자연 순서의 활용

// 1. Collections.sort
List<String> names = Arrays.asList("Charlie", "Alice", "Bob");
Collections.sort(names);
// → [Alice, Bob, Charlie]

// 2. Stream.sorted()
List<Integer> sorted = Stream.of(3, 1, 4, 1, 5, 9)
    .sorted()
    .collect(Collectors.toList());
// → [1, 1, 3, 4, 5, 9]

// 3. TreeSet
TreeSet<LocalDate> dates = new TreeSet<>();
dates.add(LocalDate.of(2025, 12, 31));
dates.add(LocalDate.of(2025, 1, 1));
dates.add(LocalDate.of(2025, 6, 15));
// 내부: [2025-01-01, 2025-06-15, 2025-12-31]

// 4. PriorityQueue
PriorityQueue<Integer> queue = new PriorityQueue<>();
queue.add(3);
queue.add(1);
queue.add(2);
queue.poll();   // 1 (가장 작음)
queue.poll();   // 2
queue.poll();   // 3

3.4 ASCII 와 String 자연 순서

// ASCII 코드 기반
"a" < "b"   // 'a' = 97, 'b' = 98
"A" < "a"   // 'A' = 65, 'a' = 97
"Z" < "a"   // 'Z' = 90, 'a' = 97

// 정렬 결과
List<String> mixed = Arrays.asList("Banana", "apple", "Cherry");
Collections.sort(mixed);
// → [Banana, Cherry, apple]
// 대문자가 먼저!

// 대소문자 무시 정렬
mixed.sort(String.CASE_INSENSITIVE_ORDER);
// → [apple, Banana, Cherry]

3.5 LocalDate 의 자연 순서

LocalDate today = LocalDate.now();
LocalDate yesterday = today.minusDays(1);
LocalDate tomorrow = today.plusDays(1);

today.compareTo(yesterday);   // 양수 (today > yesterday)
today.compareTo(tomorrow);    // 음수 (today < tomorrow)

// 정렬
List<LocalDate> dates = Arrays.asList(
    LocalDate.of(2025, 12, 31),
    LocalDate.of(2024, 1, 1),
    LocalDate.of(2025, 6, 15)
);
Collections.sort(dates);
// → [2024-01-01, 2025-06-15, 2025-12-31]

3.6 Enum 의 자연 순서

public enum ShipmentStatus {
    DRAFT,        // ordinal = 0
    PENDING,      // ordinal = 1
    ACTIVE,       // ordinal = 2
    COMPLETED,    // ordinal = 3
    CANCELLED     // ordinal = 4
}

ShipmentStatus.DRAFT.compareTo(ShipmentStatus.ACTIVE);
// → 음수 (0 - 2 = -2)

// 정렬
List<ShipmentStatus> statuses = Arrays.asList(
    ShipmentStatus.ACTIVE,
    ShipmentStatus.DRAFT,
    ShipmentStatus.COMPLETED
);
Collections.sort(statuses);
// → [DRAFT, ACTIVE, COMPLETED]

선언 순서가 곧 자연 순서.

3.7 자연 순서가 없는 경우

// 자연 순서가 의미 없는 클래스
public class Color {
    private int r, g, b;
    
    // RGB 색상 — 어떻게 비교?
    // 자연 순서가 명확하지 않음
    // → Comparable 구현 안 함
}

// 정렬 필요시 외부 Comparator
List<Color> colors = ...;
colors.sort(Comparator.comparingInt(c -> c.r));   // 빨강 강도
colors.sort(Comparator.comparingInt(c -> c.brightness()));   // 밝기

3.8 자기 점검 답변

자연 순서 (Natural Ordering) 의 의미는?

:

  • 정의:

    • Comparable.compareTo 가 정의하는 순서
    • 클래스에 내재된 정렬 기준
  • 활용:

    • Collections.sort(list)
    • TreeMap, TreeSet
    • Stream.sorted()
    • PriorityQueue
  • :

    • Integer: 숫자 오름차순
    • String: 사전순
    • LocalDate: 시간순
    • Enum: 선언 순서
  • 없는 경우:

    • 자연 순서가 의미 없으면 Comparable 구현 X
    • 외부 Comparator 활용

4️⃣ compareTo 의 5가지 계약

4.1 5가지 계약

compareTo 의 5가지 계약:

1. 반사성 (Reflexivity)
   x.compareTo(x) == 0

2. 대칭성 (Anti-symmetry)
   x.compareTo(y) == -y.compareTo(x) 의 부호

3. 추이성 (Transitivity)
   x.compareTo(y) > 0 && y.compareTo(z) > 0 => x.compareTo(z) > 0

4. 일관성 (Consistency)
   변경 없으면 같은 결과

5. equals 일치 (권장, 강제 아님)
   x.compareTo(y) == 0 <=> x.equals(y)

4.2 반사성

// x.compareTo(x) 는 항상 0

@Override
public int compareTo(Person other) {
    if (this == other) return 0;   // 명시적
    return Integer.compare(this.age, other.age);
}

Person p = new Person("Alice", 25);
p.compareTo(p);   // 0 (반사성)

4.3 대칭성 (Anti-symmetry)

// x.compareTo(y) 의 부호 == -y.compareTo(x) 의 부호

Person p1 = new Person("Alice", 25);
Person p2 = new Person("Bob", 30);

p1.compareTo(p2);   // 음수 (예: -5)
p2.compareTo(p1);   // 양수 (예: 5)
// 부호가 반대 ✓

// 위반 예
@Override
public int compareTo(Person other) {
    if (this.age < other.age) return -1;
    return 0;   // ❌ this > other 도 0 반환
    // 대칭성 위반: x.compareTo(y) = 0, y.compareTo(x) = -1
}

4.4 추이성 (Transitivity)

// x.compareTo(y) > 0 && y.compareTo(z) > 0 => x.compareTo(z) > 0

Person p1 = new Person("Alice", 30);
Person p2 = new Person("Bob", 25);
Person p3 = new Person("Charlie", 20);

p1.compareTo(p2);   // 양수 (30 > 25)
p2.compareTo(p3);   // 양수 (25 > 20)
p1.compareTo(p3);   // 양수 (30 > 20) ✓

// 위반 예 — 잘못된 로직
@Override
public int compareTo(Person other) {
    // 복잡한 조건으로 추이성 깨짐 가능
}

4.5 일관성

// 변경 없으면 같은 결과

@Override
public int compareTo(Person other) {
    // ❌ 외부 상태 의존
    return System.currentTimeMillis() % 2 == 0 
        ? Integer.compare(age, other.age) 
        : -Integer.compare(age, other.age);
    // 결과가 시간에 따라 다름
}

// ✓ 객체 자체 데이터만
@Override
public int compareTo(Person other) {
    return Integer.compare(age, other.age);
}

4.6 equals 일치 (권장)

// x.compareTo(y) == 0 <=> x.equals(y)
// 강제 아니지만 권장

// 일치하는 예
public class Person implements Comparable<Person> {
    private String name;
    private int age;
    
    @Override
    public int compareTo(Person other) {
        return Integer.compare(age, other.age);
    }
    
    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof Person)) return false;
        return age == ((Person) obj).age;
        // age 만 비교 — compareTo 와 일치
    }
}

// 불일치 예 — 위험
public class BadPerson implements Comparable<BadPerson> {
    private String name;
    private int age;
    
    @Override
    public int compareTo(BadPerson other) {
        return Integer.compare(age, other.age);   // age 만
    }
    
    @Override
    public boolean equals(Object obj) {
        // name + age 비교 — compareTo 와 불일치
        if (!(obj instanceof BadPerson)) return false;
        BadPerson p = (BadPerson) obj;
        return age == p.age && Objects.equals(name, p.name);
    }
}

// 불일치의 결과
BadPerson p1 = new BadPerson("Alice", 25);
BadPerson p2 = new BadPerson("Bob", 25);

p1.compareTo(p2);   // 0 (같음, age 만)
p1.equals(p2);      // false (name 다름)

// TreeSet 동작
TreeSet<BadPerson> set = new TreeSet<>();
set.add(p1);
set.add(p2);
set.size();   // 1! (compareTo 가 0 이라 같다고 봄)

// HashSet 동작
HashSet<BadPerson> hashSet = new HashSet<>();
hashSet.add(p1);
hashSet.add(p2);
hashSet.size();   // 2 (equals 다르다고 봄)

// → 같은 두 객체가 다른 컬렉션에서 다른 동작

4.7 BigDecimal 의 의도적 불일치

// BigDecimal 은 의도적으로 일관성 X
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("1.00");

a.compareTo(b);   // 0 (값 같음)
a.equals(b);      // false (scale 다름)

// HashSet
Set<BigDecimal> set = new HashSet<>();
set.add(a);
set.add(b);
set.size();   // 2 (equals 다름)

// TreeSet
Set<BigDecimal> tree = new TreeSet<>();
tree.add(a);
tree.add(b);
tree.size();   // 1! (compareTo 같음)

// → 일관성 권장이지만 BigDecimal 은 예외
// 사용 시 주의

4.8 자기 점검 답변

compareTo 의 5가지 계약과 equals 와의 일관성은?

:
1. 반사성: x.compareTo(x) == 0
2. 대칭성: 부호가 반대
3. 추이성: x > y, y > z → x > z
4. 일관성: 변경 없으면 같은 결과
5. equals 일치 (권장): compareTo == 0equals == true

일관성 깨지면:

  • TreeSet 과 HashSet 동작 다름
  • 같은 객체가 다른 컬렉션에서 다른 크기
  • 예: BadPerson (의도적 X), BigDecimal (의도적)

5️⃣ equals 와 compareTo 의 일관성

5.1 일관성의 정의

일관성 (Consistency):

  x.compareTo(y) == 0 <=> x.equals(y) == true

권장:
  - Comparable 의 javadoc 에서 강력 권장
  - 강제 아니지만 일관성 깨지면 문제

문제:
  - TreeMap, TreeSet 은 compareTo 사용
  - HashMap, HashSet 은 equals + hashCode 사용
  - 다른 동작 가능

5.2 일관성의 효과

// 일관된 클래스
public class Money implements Comparable<Money> {
    private BigDecimal amount;
    private String currency;
    
    @Override
    public int compareTo(Money other) {
        if (!currency.equals(other.currency)) {
            throw new IllegalArgumentException();
        }
        return amount.compareTo(other.amount);
    }
    
    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof Money)) return false;
        Money m = (Money) obj;
        return Objects.equals(currency, m.currency)
            && amount.compareTo(m.amount) == 0;   // compareTo 사용
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(currency, amount.stripTrailingZeros());
    }
}

// 사용
Money m1 = new Money(new BigDecimal("10.00"), "USD");
Money m2 = new Money(new BigDecimal("10.0"), "USD");

m1.compareTo(m2);   // 0 (값 같음)
m1.equals(m2);      // true (compareTo 기반)

Set<Money> hashSet = new HashSet<>();
hashSet.add(m1);
hashSet.add(m2);
hashSet.size();   // 1 ✓ (일관성)

Set<Money> treeSet = new TreeSet<>();
treeSet.add(m1);
treeSet.add(m2);
treeSet.size();   // 1 ✓ (일관성)

5.3 일관성 깨진 사례 — BigDecimal

// BigDecimal 은 일관성 X
BigDecimal a = new BigDecimal("100.00");
BigDecimal b = new BigDecimal("100.0");

a.compareTo(b);   // 0 (값 같음)
a.equals(b);      // false (scale 다름)

// 영향
Set<BigDecimal> hashSet = new HashSet<>();
hashSet.add(a);
hashSet.add(b);
hashSet.size();   // 2 (equals 다름)

Set<BigDecimal> treeSet = new TreeSet<>();
treeSet.add(a);
treeSet.add(b);
treeSet.size();   // 1 (compareTo 같음)

// 해결: stripTrailingZeros
BigDecimal a = new BigDecimal("100.00").stripTrailingZeros();
BigDecimal b = new BigDecimal("100.0").stripTrailingZeros();
a.equals(b);   // true ✓

5.4 일관성 검증

@Test
void testComparableEqualsConsistency() {
    Person p1 = new Person("Alice", 25);
    Person p2 = new Person("Alice", 25);
    Person p3 = new Person("Bob", 25);
    Person p4 = new Person("Alice", 30);
    
    // 같은 객체
    assertEquals(0, p1.compareTo(p2));
    assertEquals(p1, p2);
    
    // 다른 객체
    assertTrue(p1.compareTo(p3) != 0 || !p1.equals(p3));
    assertTrue(p1.compareTo(p4) != 0 || !p1.equals(p4));
    
    // 일관성
    assertTrue(
        (p1.compareTo(p2) == 0) == p1.equals(p2)
    );
}

5.5 자기 점검 답변

equals 와 compareTo 의 일관성이 왜 중요한가?

:
1. TreeSet vs HashSet 차이:

  • TreeSet: compareTo 사용
  • HashSet: equals + hashCode
  • 일관성 깨지면 다른 동작
  1. 컬렉션 동작 불일치:

    • 같은 객체 추가 시 size 다름
    • contains 결과 다름
    • 디버깅 어려움
  2. 권장 (강제 아님):

    • BigDecimal 같은 예외 존재
    • 명확한 의도가 있을 때만 깨기
  3. 검증:

    • 단위 테스트
    • 같은 필드 사용

6️⃣ 다중 필드 비교

6.1 다중 필드 비교의 필요

// 시나리오: Person 을 나이 우선, 같으면 이름순

public class Person implements Comparable<Person> {
    private String name;
    private int age;
    
    // ❌ 단일 필드만
    @Override
    public int compareTo(Person other) {
        return Integer.compare(age, other.age);
        // 같은 나이는 임의 순서
    }
}

// 개선 필요

6.2 if 문 방식

@Override
public int compareTo(Person other) {
    // 1. 나이 비교
    int cmp = Integer.compare(this.age, other.age);
    if (cmp != 0) return cmp;
    
    // 2. 같으면 이름
    return this.name.compareTo(other.name);
}

6.3 더 우아한 방식 — Comparator.thenComparing

@Override
public int compareTo(Person other) {
    return Comparator.<Person, Integer>comparing(p -> p.age)
        .thenComparing(p -> p.name)
        .compare(this, other);
}

// 또는 더 명확하게
private static final Comparator<Person> COMPARATOR = 
    Comparator.<Person>comparingInt(p -> p.age)
        .thenComparing(p -> p.name);

@Override
public int compareTo(Person other) {
    return COMPARATOR.compare(this, other);
}

6.4 자바 표준의 String 다중 비교

// String.compareTo 는 사전순
// 같으면 길이로

// 같은 패턴의 사용자 구현
public class FullName implements Comparable<FullName> {
    private String first;
    private String last;
    
    @Override
    public int compareTo(FullName other) {
        // last name 먼저
        int cmp = last.compareTo(other.last);
        if (cmp != 0) return cmp;
        
        // 같으면 first name
        return first.compareTo(other.first);
    }
}

// 정렬 예
List<FullName> names = Arrays.asList(
    new FullName("Alice", "Smith"),
    new FullName("Bob", "Adams"),
    new FullName("Alice", "Adams")
);

Collections.sort(names);
// 1. Alice Adams
// 2. Bob Adams
// 3. Alice Smith

6.5 정렬 우선순위 변경

public class Shipment implements Comparable<Shipment> {
    private int priority;
    private LocalDateTime createdAt;
    private String blNo;
    
    @Override
    public int compareTo(Shipment other) {
        // 1. priority 높은 게 먼저 (내림차순)
        int cmp = Integer.compare(other.priority, this.priority);
        if (cmp != 0) return cmp;
        
        // 2. 같으면 createdAt 빠른 게 먼저 (오름차순)
        cmp = createdAt.compareTo(other.createdAt);
        if (cmp != 0) return cmp;
        
        // 3. 같으면 blNo
        return blNo.compareTo(other.blNo);
    }
}

6.6 더 우아한 ILIC 구현

public class Shipment implements Comparable<Shipment> {
    private int priority;
    private LocalDateTime createdAt;
    private String blNo;
    
    private static final Comparator<Shipment> COMPARATOR =
        Comparator.<Shipment>comparingInt(s -> -s.priority)   // 내림차순
            .thenComparing(s -> s.createdAt)                  // 오름차순
            .thenComparing(s -> s.blNo);
    
    @Override
    public int compareTo(Shipment other) {
        return COMPARATOR.compare(this, other);
    }
}

6.7 Objects.compare 활용

// Java 7+ Objects.compare
public static <T> int compare(T a, T b, Comparator<? super T> c) {
    return (a == b) ? 0 : c.compare(a, b);
}

// 사용
@Override
public int compareTo(Person other) {
    return Objects.compare(this, other, COMPARATOR);
}

6.8 자기 점검 답변

다중 필드 비교의 우아한 방법은?

:
1. if 문 방식 (단순):

int cmp = field1.compareTo(other.field1);
if (cmp != 0) return cmp;
return field2.compareTo(other.field2);
  1. Comparator.thenComparing (권장):

    Comparator.comparing(Person::getAge)
        .thenComparing(Person::getName)
  2. 정적 필드로 추출:

    private static final Comparator<Person> COMP = ...;
  3. 정렬 방향 조정:

    .reversed()  // 내림차순

7️⃣ TreeMap, TreeSet, Collections.sort 의 활용

7.1 TreeSet — 자동 정렬 Set

TreeSet<Integer> set = new TreeSet<>();
set.add(5);
set.add(2);
set.add(8);
set.add(1);

// 내부: [1, 2, 5, 8] — 자동 정렬

set.first();   // 1
set.last();    // 8

// 범위 검색
set.headSet(5);   // [1, 2]
set.tailSet(5);   // [5, 8]
set.subSet(2, 8); // [2, 5]

// 정렬된 순회
for (Integer n : set) {
    System.out.println(n);
}
// 1, 2, 5, 8

7.2 TreeMap — 자동 정렬 Map

TreeMap<LocalDate, BigDecimal> dailySales = new TreeMap<>();
dailySales.put(LocalDate.of(2025, 1, 15), new BigDecimal("1000"));
dailySales.put(LocalDate.of(2025, 1, 1), new BigDecimal("500"));
dailySales.put(LocalDate.of(2025, 1, 10), new BigDecimal("750"));

// 키 기준 자동 정렬
dailySales.firstKey();   // 2025-01-01
dailySales.lastKey();    // 2025-01-15

// 범위 조회
dailySales.headMap(LocalDate.of(2025, 1, 10));   // [01-01]
dailySales.tailMap(LocalDate.of(2025, 1, 10));   // [01-10, 01-15]

// 순회 (정렬된 순서)
for (Map.Entry<LocalDate, BigDecimal> entry : dailySales.entrySet()) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}

7.3 Collections.sort

List<Integer> list = new ArrayList<>(Arrays.asList(5, 2, 8, 1));
Collections.sort(list);
// list: [1, 2, 5, 8]

// List.sort 도 같음
list.sort(null);   // null = 자연 순서

// Stream
List<Integer> sorted = list.stream().sorted().toList();

7.4 PriorityQueue — 우선순위 큐

PriorityQueue<Integer> queue = new PriorityQueue<>();
queue.offer(5);
queue.offer(2);
queue.offer(8);
queue.offer(1);

queue.poll();   // 1 (가장 작음)
queue.poll();   // 2
queue.poll();   // 5
queue.poll();   // 8

// 내부 구조: heap
// add/remove: O(log n)
// peek: O(1)

7.5 Collections.max / min

List<Integer> list = Arrays.asList(5, 2, 8, 1, 9, 3);

Integer max = Collections.max(list);   // 9
Integer min = Collections.min(list);   // 1

// 객체에도 적용
List<Person> people = ...;
Person youngest = Collections.min(people);   // 자연 순서 사용
Person oldest = Collections.max(people);

7.6 Collections.binarySearch

// 정렬된 List 에서 이진 검색
List<Integer> sorted = Arrays.asList(1, 2, 5, 8, 9);

int idx = Collections.binarySearch(sorted, 5);
// 2 (인덱스)

int idx2 = Collections.binarySearch(sorted, 4);
// -3 (음수 = 없음, 절댓값 - 1 = 삽입 위치)

// O(log n) — 빠른 검색
// 조건: 자연 순서로 정렬되어 있어야

7.7 자연 순서 활용 종합

// ILIC 예
public class ShipmentService {
    
    public List<Shipment> getOrderedShipments() {
        List<Shipment> shipments = repository.findAll();
        Collections.sort(shipments);   // Comparable 활용
        return shipments;
    }
    
    public Shipment getOldestShipment() {
        return Collections.min(repository.findAll());
    }
    
    public TreeSet<Shipment> getShipmentsSortedSet() {
        return new TreeSet<>(repository.findAll());
    }
    
    public TreeMap<LocalDate, List<Shipment>> getShipmentsByDate() {
        return repository.findAll().stream()
            .collect(Collectors.groupingBy(
                Shipment::getCreatedAt,
                TreeMap::new,   // 자동 정렬
                Collectors.toList()
            ));
    }
}

7.8 자기 점검 답변

Comparable 을 활용하는 자바 컬렉션 5가지는?

:
1. TreeSet: 자동 정렬 Set
2. TreeMap: 키 자동 정렬 Map
3. PriorityQueue: 우선순위 큐 (힙)
4. Collections.sort: List 정렬
5. Collections.max/min/binarySearch: 검색

추가:

  • Stream.sorted()
  • Arrays.sort()

→ 자바 컬렉션의 정렬 + 검색의 토대.


8️⃣ 자바 표준의 Comparable 분석

8.1 Integer.compareTo

public final class Integer implements Comparable<Integer> {
    
    @Override
    public int compareTo(Integer other) {
        return compare(this.value, other.value);
    }
    
    public static int compare(int x, int y) {
        return (x < y) ? -1 : ((x == y) ? 0 : 1);
    }
}

특징:

  • 안전한 비교 (오버플로우 X)
  • 정적 메서드 활용
  • -1, 0, 1 반환

8.2 String.compareTo

public final class String implements Comparable<String> {
    
    @Override
    public int compareTo(String other) {
        int len1 = value.length;
        int len2 = other.value.length;
        int lim = Math.min(len1, len2);
        
        // 1. 문자 비교
        for (int i = 0; i < lim; i++) {
            char c1 = value[i];
            char c2 = other.value[i];
            if (c1 != c2) {
                return c1 - c2;   // 문자 코드 차이
            }
        }
        
        // 2. 모든 비교 후 길이 비교
        return len1 - len2;
    }
}

특징:

  • 문자별 비교
  • 짧으면 길이 차이
  • 사전순

8.3 LocalDate.compareTo

public final class LocalDate implements ChronoLocalDate {
    
    @Override
    public int compareTo(ChronoLocalDate other) {
        if (other instanceof LocalDate) {
            return compareTo0((LocalDate) other);
        }
        return ChronoLocalDate.super.compareTo(other);
    }
    
    private int compareTo0(LocalDate other) {
        int cmp = (year - other.year);
        if (cmp == 0) {
            cmp = (month - other.month);
            if (cmp == 0) {
                cmp = (day - other.day);
            }
        }
        return cmp;
    }
}

특징:

  • 년 → 월 → 일 순서로 비교
  • 다중 필드 비교의 모범

8.4 BigDecimal.compareTo

public class BigDecimal extends Number implements Comparable<BigDecimal> {
    
    @Override
    public int compareTo(BigDecimal other) {
        // scale 무관 비교
        if (scale == other.scale) {
            long s1 = intCompact;
            long s2 = other.intCompact;
            if (s1 != INFLATED && s2 != INFLATED) {
                return Long.compare(s1, s2);
            }
        }
        // 복잡한 scale 정규화 후 비교
        // ...
    }
}

// equals 와 의도적으로 다름
// equals: scale 도 비교
// compareTo: 값만 비교

8.5 Character.compareTo

public final class Character implements Comparable<Character> {
    
    @Override
    public int compareTo(Character other) {
        return compare(this.value, other.value);
    }
    
    public static int compare(char x, char y) {
        return x - y;
        // char 는 unsigned 16-bit, 오버플로우 안전
    }
}

8.6 Enum.compareTo

public abstract class Enum<E extends Enum<E>> implements Comparable<E> {
    
    @Override
    public final int compareTo(E other) {
        Enum<?> first = this;
        Enum<?> second = other;
        if (first.getDeclaringClass() != second.getDeclaringClass()) {
            throw new ClassCastException();
        }
        return first.ordinal() - second.ordinal();
    }
}

// final 이라 override 불가
// 선언 순서로 비교

8.7 비교 종합

클래스비교 기준특이사항
Integer안전한 compare
Long안전한 compare
DoubleNaN 처리
String문자 + 길이사전순
LocalDate년/월/일다중 필드
BigDecimal값 (scale 무관)equals 와 불일치
Characterchar 값unsigned
Enumordinalfinal, 선언 순서

8.8 자기 점검 답변

자바 표준 Comparable 구현의 모범 패턴은?

:
1. Integer.compareTo:

  • 정적 compare 메서드
  • 안전한 비교 (오버플로우 X)
  1. String.compareTo:

    • 문자별 비교
    • 길이 차이로 마무리
  2. LocalDate.compareTo:

    • 년 → 월 → 일 다중 필드
    • if 문 방식
  3. BigDecimal.compareTo:

    • 의도적으로 equals 와 불일치
    • scale 무관 값 비교
  4. Enum.compareTo:

    • final (override 불가)
    • ordinal 기반

→ 모두 안전성 + 명확성.


9️⃣ 면접 + 자기 점검

9.1 면접 단골 질문 매핑

Q핵심 답변
Comparable 정의?java.lang, 단일 메서드
compareTo 반환값?음수/0/양수 (부호만 중요)
자연 순서?Comparable 이 정의하는 순서
compareTo 5가지 계약?반사/대칭/추이/일관/equals 일치
equals 와 일관성?TreeSet vs HashSet 동작 일치
BigDecimal 의 예외?scale 무관 compareTo
다중 필드 비교?if 문 또는 thenComparing
Integer.compare 사용 이유?오버플로우 안전
TreeMap, TreeSet 활용?Comparable 자동 사용
Enum compareTo?ordinal, final
Comparable 인스턴스화?불가 (인터페이스)

9.2 자기 점검 체크리스트

기본 이해

  • Comparable 인터페이스 정의
  • compareTo 의 반환값
  • 자연 순서의 의미
  • 자바 표준의 Comparable 구현 (Integer, String, ...)

계약

  • 5가지 계약 (반사/대칭/추이/일관/equals)
  • equals 와의 일관성
  • BigDecimal 의 예외
  • 위반 시 결과

구현

  • 단일 필드 비교
  • 다중 필드 비교 (if 문)
  • 다중 필드 비교 (thenComparing)
  • Integer.compare 활용

활용

  • Collections.sort
  • TreeSet, TreeMap
  • PriorityQueue
  • Stream.sorted
  • Collections.max/min/binarySearch

9.3 추가 심화 질문

Q1: Comparable 의 T 는 보통 자기 자신인 이유?

답:

  • 자기 타입과 비교가 일반적
  • Person implements Comparable<Person>
  • 다른 타입과 비교는 드물고 복잡 (Child implements Comparable)
  • 자기 타입이 가장 자연스러운 의미

Q2: compareTo 가 일관성 깨지면?

답:

  • 정렬 결과 예측 불가
  • TreeSet 의 동작 불안정
  • 같은 정렬을 두 번 했을 때 다른 결과
  • 디버깅 매우 어려움

Q3: Comparable 과 함수형 인터페이스?

답:

  • Comparable 은 함수형 인터페이스 X
  • 추상 메서드 1개 (조건 충족)
  • 하지만 람다로 잘 안 씀:
    • 클래스의 정체성에 의존
    • 람다 = 익명 함수, 클래스 정체성 X
  • Comparator 가 함수형 인터페이스로 권장

Q4: compareTo 의 결과를 어떻게 사용?

답:

int cmp = a.compareTo(b);

if (cmp < 0) { ... }   // a < b
if (cmp == 0) { ... }  // a == b
if (cmp > 0) { ... }   // a > b

// 또는 부호만
int sign = Integer.signum(cmp);

Q5: Comparable 을 구현 안 하면?

답:

  • Collections.sort(list) → ClassCastException
  • TreeSet add → ClassCastException
  • 외부 Comparator 필요
  • Comparator.comparing 또는 직접 구현
// Comparable 없을 때
public class Color { ... }

List<Color> colors = ...;
Collections.sort(colors);   // ❌ ClassCastException

// 해결: 외부 Comparator
colors.sort(Comparator.comparingInt(c -> c.brightness()));

🎯 핵심 요약 — 3줄 정리

1. Comparable

  • "자기 자신의 비교 기준" 정의
  • compareTo: 음수/0/양수
  • 자연 순서 (Natural Ordering)

2. 5가지 계약

  • 반사/대칭/추이/일관
  • equals 와의 일관성 (권장)
  • BigDecimal 은 예외

3. 활용

  • Collections.sort, TreeSet, TreeMap
  • PriorityQueue, binarySearch
  • Integer.compare 로 안전한 비교

📚 다음으로...

Unit 6.3 — Comparator 의 외부 비교

이번 Unit에서 Comparable 을 봤다면, 다음은 Comparator 의 정밀.

  • Comparator 의 정의
  • 함수형 인터페이스로서의 활용
  • comparing, thenComparing, reversed
  • naturalOrder, reverseOrder
  • null 처리 (nullsFirst, nullsLast)

Phase 6 진행 상황

🚀 Phase 6 — 객체 비교
  ✅ Unit 6.1 equals 와 hashCode 의 계약
  ✅ Unit 6.2 Comparable<T> 의 자연 순서 ← 여기
  ⏭ Unit 6.3 Comparator<T> 의 외부 비교
  ⏭ Unit 6.4 비교의 종합 활용 (마스터 깊이)

3주차 누적 진행

✅ Phase 1 — Pass by Value (1.1 ~ 1.3 완주)
✅ Phase 2 — 컬렉션 프레임워크 (2.1 ~ 2.6 완주)
✅ Phase 3 — 해시의 원리 (3.1 ~ 3.4 완주)
✅ Phase 4 — 추상화의 두 도구 (4.1 ~ 4.4 완주)
✅ Phase 5 — 제네릭과 와일드카드 (5.1 ~ 5.5 완주)
🚀 Phase 6 — 객체 비교 (2/4 진행)

총: 24/43 Unit 작성 (약 56%)
profile
Software Developer

0개의 댓글