F-LAB JAVA · 3주차 · Phase 6 · 객체 비교
이 Unit을 끝내면 다음을 답할 수 있어야 한다.
Comparable<T> 인터페이스 의 정확한 정의는?compareTo 메서드 의 반환값 의미는?equals 와 compareTo 의 일관성 의 의미와 중요성은?Integer.compare() 같은 정적 메서드를 쓰는 이유는?
Comparable<T>는 "자기 자신을 어떻게 비교하는가" 를 정의하는 인터페이스다.
compareTo메서드가 음수 (this < other), 0 (같음), 양수 (this > other) 를 반환하며,
이로 인해 자연 순서 (Natural Ordering) 가 정의된다.
Collections.sort(list),TreeMap,TreeSet이 이 순서를 활용해 자동 정렬.
equals와compareTo의 일관성 을 지키지 않으면 TreeSet 의 contains 와 HashSet 의 contains 가 다른 결과를 반환.
Comparable = 학생 스스로 결정한 순위 기준
Student implements Comparable<Student> {
compareTo(other):
- 내 점수가 높으면 양수 (나는 더 위)
- 내 점수가 낮으면 음수 (나는 더 아래)
- 같으면 0
이로 인해:
- 학생들을 한 줄로 세울 수 있음 (자연 순서)
- Collections.sort 가 자동으로 정렬
- TreeMap 이 학생을 키로 자동 정렬 보관
→ Comparable = 자기 정렬 기준 + 자연 순서.
1. Comparable<T> 인터페이스의 정의
2. compareTo 의 반환값
3. 자연 순서 (Natural Ordering)
4. compareTo 의 5가지 계약
5. equals 와 compareTo 의 일관성
6. 다중 필드 비교
7. TreeMap, TreeSet, Collections.sort 의 활용
8. 자바 표준의 Comparable 분석
9. 면접 + 자기 점검
package java.lang;
public interface Comparable<T> {
int compareTo(T other);
}
핵심:
java.lang 패키지 (기본 import)compareTopublic 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)
// 권장: 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);
}
}
// 모든 숫자 타입
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();
// 선언 순서 기반
}
}
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
// 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) { ... }
Comparable 의 본질과 정의는?
답:
본질:
정의:
java.lang 패키지compareTo(T)구현체:
→ Java 의 기본 비교 메커니즘.
compareTo(other) 의 반환값:
- **음수** (보통 -1): this < other (this 가 더 작음)
- **0**: this == other (같음)
- **양수** (보통 1): this > other (this 가 더 큼)
정확한 수치는 무관, 부호만 중요.
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); // 양수
// 자바 표준은 정확한 수치 명시 안 함
// 예: 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());
// 일 수 차이
}
// 다음 모든 표현이 같은 의미:
// "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 가 큼
}
// ❌ 잘못 — 오버플로우 가능
@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);
}
// 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);
}
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 사용 시 비교 방법 주의.
compareTo 의 반환값과 안전한 비교 방법은?
답:
반환값:
흔한 실수:
a - b → 오버플로우 위험권장:
Integer.compare(a, b) 등 정적 메서드자연 순서 (Natural Ordering):
Comparable 의 compareTo 가 정의하는 순서.
특징:
- 클래스에 내재된 순서
- 외부 Comparator 불필요
- Collections.sort(list) 가 자동 활용
- TreeMap, TreeSet 의 기본 순서
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
// 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
// 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]
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]
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]
→ 선언 순서가 곧 자연 순서.
// 자연 순서가 의미 없는 클래스
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())); // 밝기
자연 순서 (Natural Ordering) 의 의미는?
답:
정의:
활용:
예:
없는 경우:
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)
// 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 (반사성)
// 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
}
// 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) {
// 복잡한 조건으로 추이성 깨짐 가능
}
// 변경 없으면 같은 결과
@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);
}
// 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 다르다고 봄)
// → 같은 두 객체가 다른 컬렉션에서 다른 동작
// 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 은 예외
// 사용 시 주의
compareTo 의 5가지 계약과 equals 와의 일관성은?
답:
1. 반사성: x.compareTo(x) == 0
2. 대칭성: 부호가 반대
3. 추이성: x > y, y > z → x > z
4. 일관성: 변경 없으면 같은 결과
5. equals 일치 (권장): compareTo == 0 ↔ equals == true
일관성 깨지면:
일관성 (Consistency):
x.compareTo(y) == 0 <=> x.equals(y) == true
권장:
- Comparable 의 javadoc 에서 강력 권장
- 강제 아니지만 일관성 깨지면 문제
문제:
- TreeMap, TreeSet 은 compareTo 사용
- HashMap, HashSet 은 equals + hashCode 사용
- 다른 동작 가능
// 일관된 클래스
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 ✓ (일관성)
// 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 ✓
@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)
);
}
equals 와 compareTo 의 일관성이 왜 중요한가?
답:
1. TreeSet vs HashSet 차이:
컬렉션 동작 불일치:
권장 (강제 아님):
검증:
// 시나리오: 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);
// 같은 나이는 임의 순서
}
}
// 개선 필요
@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);
}
@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);
}
// 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
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);
}
}
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);
}
}
// 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);
}
다중 필드 비교의 우아한 방법은?
답:
1. if 문 방식 (단순):
int cmp = field1.compareTo(other.field1);
if (cmp != 0) return cmp;
return field2.compareTo(other.field2);
Comparator.thenComparing (권장):
Comparator.comparing(Person::getAge)
.thenComparing(Person::getName)
정적 필드로 추출:
private static final Comparator<Person> COMP = ...;
정렬 방향 조정:
.reversed() // 내림차순
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
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());
}
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();
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)
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);
// 정렬된 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) — 빠른 검색
// 조건: 자연 순서로 정렬되어 있어야
// 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()
));
}
}
Comparable 을 활용하는 자바 컬렉션 5가지는?
답:
1. TreeSet: 자동 정렬 Set
2. TreeMap: 키 자동 정렬 Map
3. PriorityQueue: 우선순위 큐 (힙)
4. Collections.sort: List 정렬
5. Collections.max/min/binarySearch: 검색
추가:
→ 자바 컬렉션의 정렬 + 검색의 토대.
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);
}
}
특징:
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;
}
}
특징:
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;
}
}
특징:
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: 값만 비교
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, 오버플로우 안전
}
}
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 불가
// 선언 순서로 비교
| 클래스 | 비교 기준 | 특이사항 |
|---|---|---|
| Integer | 값 | 안전한 compare |
| Long | 값 | 안전한 compare |
| Double | 값 | NaN 처리 |
| String | 문자 + 길이 | 사전순 |
| LocalDate | 년/월/일 | 다중 필드 |
| BigDecimal | 값 (scale 무관) | equals 와 불일치 |
| Character | char 값 | unsigned |
| Enum | ordinal | final, 선언 순서 |
자바 표준 Comparable 구현의 모범 패턴은?
답:
1. Integer.compareTo:
compare 메서드String.compareTo:
LocalDate.compareTo:
BigDecimal.compareTo:
Enum.compareTo:
→ 모두 안전성 + 명확성.
| 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 인스턴스화? | 불가 (인터페이스) |
답:
Person implements Comparable<Person>답:
답:
답:
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);
답:
// Comparable 없을 때
public class Color { ... }
List<Color> colors = ...;
Collections.sort(colors); // ❌ ClassCastException
// 해결: 외부 Comparator
colors.sort(Comparator.comparingInt(c -> c.brightness()));
1. Comparable
2. 5가지 계약
3. 활용
이번 Unit에서 Comparable 을 봤다면, 다음은 Comparator 의 정밀.
🚀 Phase 6 — 객체 비교
✅ Unit 6.1 equals 와 hashCode 의 계약
✅ Unit 6.2 Comparable<T> 의 자연 순서 ← 여기
⏭ Unit 6.3 Comparator<T> 의 외부 비교
⏭ Unit 6.4 비교의 종합 활용 (마스터 깊이)
✅ 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%)