3주차 Unit 6.4 — 비교의 종합 활용

Psj·2026년 5월 19일

F-lab

목록 보기
100/239

Unit 6.4 — 비교의 종합 활용

F-LAB JAVA · 3주차 · Phase 6 · 객체 비교
🎯 마스터 프롬프트 깊이 Unit — 🏆 Phase 6 완주


📌 학습 목표

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

  • equals + hashCode + Comparable + Comparator 의 4가지 비교 메커니즘 통합은?
  • HashSet vs TreeSet 의 동작 차이 가 정확히 어떻게 발생하나?
  • HashMap vs TreeMap 의 동작 차이 의 본질은?
  • equals/hashCode/compareTo 의 일관성 깨질 때 모든 컬렉션의 동작은?
  • Collections.sort, binarySearch 의 정밀 동작 은?
  • PriorityQueue 의 힙 구조 와 비교 메커니즘은?
  • Timsort, Dual-Pivot QuickSort 알고리즘과 Comparator 의 역할은?
  • 실무 패턴 종합 으로 어떤 비교 도구를 언제 쓰나?
  • Phase 6 의 50가지 졸업 시험 통과 가능?

🎯 핵심 한 문장

자바의 객체 비교는 4가지 메커니즘 — equals, hashCode, Comparable, Comparator — 의 정밀한 협조로 동작한다.
HashSet/HashMap 은 hashCode → equals 순서로, TreeSet/TreeMap 은 compareTo 또는 외부 Comparator 로만 비교.
같은 객체라도 어떤 컬렉션에 넣느냐에 따라 동작이 다르며, 일관성 을 깨면 예상 못한 버그가 발생.
Collections.sort 는 Timsort (안정 정렬), Arrays.sort(int[]) 는 Dual-Pivot QuickSort (불안정) 를 사용하며,
PriorityQueue 는 이진 힙 (Binary Heap) 자료구조로 O(log n) 우선순위 처리.

비유 — 네 가지 ID 카드

한 사람을 식별하는 4가지 방법:

equals = "같은 사람인가" 신원 확인
hashCode = "분류 번호 (지문 인식)"
Comparable = "키 순서로 줄 서기"
Comparator = "다양한 기준으로 줄 세우기 (심판)"

각각의 컬렉션이 어느 방법으로:
- HashSet/HashMap: 지문 인식 → 신원 확인 (hashCode → equals)
- TreeSet/TreeMap: 키 순서 줄 세우기 (compareTo)
- PriorityQueue: 외부 심판 (Comparator) 또는 자신 (Comparable)
- ArrayList: 신원 확인만 (equals)

→ 4가지 메커니즘의 정밀한 협조.


🧭 9개 섹션 로드맵

1. 비교의 4가지 메커니즘 통합
2. HashSet vs TreeSet 동작 정밀 분석
3. HashMap vs TreeMap 동작 정밀 분석
4. equals + hashCode + Comparable 일관성 종합
5. Collections.sort, binarySearch 의 정밀
6. PriorityQueue 의 동작 메커니즘
7. 정렬 알고리즘과 Comparator (Timsort, Dual-Pivot QuickSort)
8. 실무 패턴 종합 + Phase 6 졸업 시험 (50 문항)
9. Phase 6 완주 + Phase 7 (I/O) 예고

1️⃣ 비교의 4가지 메커니즘 통합

1.1 4가지 메커니즘 종합 표

메커니즘용도위치패키지결과
==참조 비교연산자(없음)boolean
equals논리 동등성Object.equalsjava.langboolean
hashCode정수 표현Object.hashCodejava.langint
Comparable.compareTo자연 순서java.lang.Comparablejava.langint (음수/0/양수)
Comparator.compare외부 순서java.util.Comparatorjava.utilint (음수/0/양수)

1.2 컬렉션별 사용 메커니즘

컬렉션 → 사용 메커니즘

ArrayList, LinkedList:
  - 인덱스 기반
  - contains, indexOf 시 equals
  - sort 시 Comparable 또는 Comparator

HashSet, LinkedHashSet:
  - hashCode → equals
  - 순서 보장 X (또는 삽입 순서, LinkedHashSet)

TreeSet:
  - Comparable 또는 Comparator
  - equals 무시 (compareTo == 0 만 보고 같다고 판단)
  - 자동 정렬

HashMap, LinkedHashMap:
  - 키의 hashCode → equals
  - 순서 보장 X (또는 삽입 순서, LinkedHashMap)

TreeMap:
  - 키의 Comparable 또는 Comparator
  - equals 무시 (키의 compareTo == 0 만 보고 같다고 판단)
  - 자동 정렬

PriorityQueue:
  - Comparable 또는 Comparator
  - 힙 구조 유지

1.3 같은 객체, 다른 컬렉션의 동작

// 일관성 깨진 클래스
public class Person {
    private String name;
    private int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof Person)) return false;
        Person p = (Person) obj;
        return name.equals(p.name) && age == p.age;
        // 이름 + 나이 비교
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

// Comparator (외부)
Comparator<Person> byAge = Comparator.comparing(Person::getAge);

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

// HashSet — equals 사용
Set<Person> hashSet = new HashSet<>();
hashSet.add(p1);
hashSet.add(p2);
hashSet.size();   // 2 (name 다름 → equals false)

// TreeSet (with Comparator) — compare 사용
Set<Person> treeSet = new TreeSet<>(byAge);
treeSet.add(p1);
treeSet.add(p2);
treeSet.size();   // 1! (age 같음 → compare == 0)

// → 같은 객체, 다른 컬렉션, 다른 동작

1.4 비교 메커니즘 선택 가이드

시나리오별 선택:

1. "같은 객체인가?"
   → == (참조)
   
2. "같은 내용인가?"
   → equals
   
3. "분류용 정수?"
   → hashCode

4. "자연 순서로 정렬?"
   → Comparable

5. "다양한 기준으로 정렬?"
   → Comparator

6. "Set 으로 중복 제거?"
   → equals + hashCode (HashSet)
   → Comparable/Comparator (TreeSet)

7. "Map 으로 키-값?"
   → equals + hashCode (HashMap)
   → Comparable/Comparator (TreeMap)

1.5 ILIC 종합 사용

public class Shipment implements Comparable<Shipment> {
    
    @Id
    private Long id;
    private String blNo;
    private BigDecimal weight;
    private LocalDateTime createdAt;
    private int priority;
    
    // 1. equals — id 기반 (JPA 권장)
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (!(obj instanceof Shipment)) return false;
        return id != null && id.equals(((Shipment) obj).id);
    }
    
    // 2. hashCode — 클래스 기반 (영속 전 일관)
    @Override
    public int hashCode() {
        return getClass().hashCode();
    }
    
    // 3. Comparable — 자연 순서 (id)
    @Override
    public int compareTo(Shipment other) {
        return Long.compare(this.id, other.id);
    }
}

// 4. 다양한 Comparator
public class ShipmentComparators {
    public static final Comparator<Shipment> BY_WEIGHT = 
        Comparator.comparing(Shipment::getWeight);
    
    public static final Comparator<Shipment> BY_CREATED_AT = 
        Comparator.comparing(Shipment::getCreatedAt);
    
    public static final Comparator<Shipment> URGENT_FIRST = 
        Comparator.comparingInt(Shipment::getPriority).reversed();
}

// 활용
Shipment s1 = ...;
Shipment s2 = ...;

// 다양한 비교
s1.equals(s2);                  // id 비교
s1.compareTo(s2);                // id 순서
ShipmentComparators.BY_WEIGHT.compare(s1, s2);   // 무게 비교

// 컬렉션
HashSet<Shipment> uniqueById = new HashSet<>();   // id 중복 제거
TreeSet<Shipment> byNatural = new TreeSet<>();     // id 자연 순서
TreeSet<Shipment> byWeight = new TreeSet<>(ShipmentComparators.BY_WEIGHT);
PriorityQueue<Shipment> urgent = new PriorityQueue<>(ShipmentComparators.URGENT_FIRST);

1.6 자기 점검 답변

4가지 비교 메커니즘의 통합적 활용은?

:
1. ==: 참조 비교 (메모리 주소)
2. equals: 논리 동등성
3. hashCode: 정수 표현
4. Comparable.compareTo: 자연 순서
5. Comparator.compare: 외부 순서

컬렉션별 사용:

  • HashSet/HashMap: hashCode → equals
  • TreeSet/TreeMap: compareTo 또는 Comparator
  • PriorityQueue: Comparable 또는 Comparator

일관성의 중요성:

  • 깨지면 컬렉션마다 다른 동작
  • 디버깅 매우 어려움

2️⃣ HashSet vs TreeSet 동작 정밀 분석

2.1 HashSet 의 내부 동작

public class HashSet<E> {
    private HashMap<E, Object> map;
    private static final Object PRESENT = new Object();
    
    public boolean add(E e) {
        return map.put(e, PRESENT) == null;
    }
}

// HashMap 의 put
public V put(K key, V value) {
    // 1. hashCode 계산
    int hash = hash(key);
    
    // 2. 버킷 찾기
    int index = hash & (table.length - 1);
    Node<K, V> node = table[index];
    
    // 3. 같은 키 검사 (equals)
    if (node != null && node.hash == hash 
            && (node.key == key || (key != null && key.equals(node.key)))) {
        return node.value;   // 이미 있음
    }
    
    // 4. 새 노드 추가
    // ...
}

핵심:

  • hashCode 로 버킷 찾기
  • equals 로 정확히 비교
  • O(1) 평균 (collision 적을 때)

2.2 TreeSet 의 내부 동작

public class TreeSet<E> {
    private TreeMap<E, Object> m;
    private static final Object PRESENT = new Object();
    
    public boolean add(E e) {
        return m.put(e, PRESENT) == null;
    }
}

// TreeMap 의 put (Red-Black Tree)
public V put(K key, V value) {
    Entry<K, V> t = root;
    
    if (t == null) {
        // 첫 노드
        root = new Entry<>(key, value, null);
        size = 1;
        return null;
    }
    
    int cmp;
    Comparator<? super K> cpr = comparator;
    
    // ★ 비교 방법:
    // - Comparator 가 있으면 cpr.compare(key, t.key)
    // - 없으면 key 의 compareTo(t.key) 사용 (Comparable)
    
    if (cpr != null) {
        // Comparator 사용
        do {
            parent = t;
            cmp = cpr.compare(key, t.key);
            if (cmp < 0) t = t.left;
            else if (cmp > 0) t = t.right;
            else return t.setValue(value);   // 이미 있음
        } while (t != null);
    } else {
        // Comparable 사용
        Comparable<? super K> k = (Comparable<? super K>) key;
        do {
            parent = t;
            cmp = k.compareTo(t.key);
            // ...
        } while (t != null);
    }
    
    // 새 노드 추가 + Red-Black 균형 조정
}

핵심:

  • compareTo 또는 Comparator 만 사용
  • equals 무시!
  • O(log n) — Red-Black Tree

2.3 결정적 차이 — equals vs compareTo

public class Money {
    private BigDecimal amount;
    
    public Money(BigDecimal amount) {
        this.amount = amount;
    }
    
    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof Money)) return false;
        return Objects.equals(amount, ((Money) obj).amount);
    }
    
    @Override
    public int hashCode() {
        return amount.hashCode();
    }
}

// BigDecimal 자체의 일관성 깨짐
Money m1 = new Money(new BigDecimal("100.00"));
Money m2 = new Money(new BigDecimal("100.0"));

m1.equals(m2);   // false! (BigDecimal.equals 는 scale 비교)
// 하지만 m1.amount.compareTo(m2.amount) == 0 (값 같음)

// HashSet
Set<Money> hashSet = new HashSet<>();
hashSet.add(m1);
hashSet.add(m2);
hashSet.size();   // 2 (equals 다름)

// TreeSet with Comparator
Comparator<Money> byAmount = (a, b) -> a.amount.compareTo(b.amount);
Set<Money> treeSet = new TreeSet<>(byAmount);
treeSet.add(m1);
treeSet.add(m2);
treeSet.size();   // 1! (compareTo 0)

// → 같은 두 객체가 다른 동작

2.4 시간 복잡도 비교

작업HashSetTreeSet
addO(1) 평균O(log n)
removeO(1) 평균O(log n)
containsO(1) 평균O(log n)
순회O(n), 순서 XO(n), 정렬된 순서
추가 메서드(없음)first, last, headSet, tailSet, subSet

2.5 사용 시점

HashSet 선택:
  ✓ 단순 중복 제거
  ✓ 빠른 검색 필요
  ✓ 순서 무관
  ✓ equals + hashCode 정의됨

TreeSet 선택:
  ✓ 자동 정렬 필요
  ✓ 범위 검색 (headSet, tailSet)
  ✓ 첫/마지막 요소 (first, last)
  ✓ Comparable 또는 Comparator 있음

LinkedHashSet:
  ✓ 삽입 순서 보장
  ✓ 빠른 검색 (HashSet 기반)
  ✓ HashSet 의 비결정 순서 해결

2.6 디버깅 시 흔한 함정

// 함정 1: HashSet 에 mutable 객체
public class MutablePerson {
    public String name;
    
    @Override
    public boolean equals(Object obj) { ... }
    
    @Override
    public int hashCode() {
        return name.hashCode();
    }
}

Set<MutablePerson> set = new HashSet<>();
MutablePerson p = new MutablePerson();
p.name = "Alice";
set.add(p);

p.name = "Bob";   // ★ 변경
set.contains(p);   // false! (hashCode 변경됨)
set.remove(p);      // ❌ 못 지움 → memory leak

// 해결: 불변 객체

// 함정 2: TreeSet 에서 equals 와 compareTo 불일치
public class BadClass implements Comparable<BadClass> {
    @Override
    public int compareTo(BadClass other) {
        return 0;   // 모두 같다고 함
    }
    
    @Override
    public boolean equals(Object obj) {
        return false;   // 모두 다르다고 함
    }
}

TreeSet<BadClass> set = new TreeSet<>();
set.add(new BadClass());
set.add(new BadClass());
set.size();   // 1! (compareTo 0)

// HashSet
Set<BadClass> hashSet = new HashSet<>();
hashSet.add(new BadClass());
hashSet.add(new BadClass());
hashSet.size();   // 2 (equals false)

// → 일관성 깨짐

2.7 자기 점검 답변

HashSet vs TreeSet 의 결정적 차이는?

:
1. 비교 방법:

  • HashSet: hashCode → equals
  • TreeSet: compareTo 또는 Comparator (equals 무시!)
  1. 자료 구조:

    • HashSet: 해시 테이블 + 버킷
    • TreeSet: Red-Black Tree
  2. 시간 복잡도:

    • HashSet: O(1) 평균
    • TreeSet: O(log n)
  3. 순서:

    • HashSet: 무순서
    • TreeSet: 정렬된 순서
  4. 결과 차이:

    • 일관성 깨지면 다른 동작
    • BigDecimal 같은 의도적 예외

3️⃣ HashMap vs TreeMap 동작 정밀 분석

3.1 HashMap 의 내부 동작

public class HashMap<K, V> {
    Node<K, V>[] table;   // 버킷 배열
    int size;
    int threshold;        // resize 트리거
    float loadFactor;     // 기본 0.75
    
    public V put(K key, V value) {
        int hash = hash(key);   // hashCode 변형
        int index = hash & (table.length - 1);   // 버킷 인덱스
        
        Node<K, V> node = table[index];
        
        if (node == null) {
            // 빈 버킷 — 새 노드
            table[index] = new Node<>(hash, key, value, null);
        } else {
            // collision — equals 로 확인
            Node<K, V> existing = findNode(node, hash, key);
            if (existing != null) {
                V old = existing.value;
                existing.value = value;
                return old;
            }
            // 추가 (LinkedList 또는 Tree)
        }
        
        if (++size > threshold) {
            resize();   // 2배 확장
        }
        return null;
    }
}

핵심:

  • hashCode 로 버킷 찾기
  • equals 로 동일 키 확인
  • collision 시 LinkedList 또는 Tree
  • Java 8+ : 버킷 길이 ≥ 8 이면 Tree 변환

3.2 TreeMap 의 내부 동작

public class TreeMap<K, V> {
    private Entry<K, V> root;        // Red-Black Tree 루트
    private final Comparator<? super K> comparator;
    
    public V put(K key, V value) {
        Entry<K, V> t = root;
        
        if (t == null) {
            // 첫 노드
            if (comparator == null) {
                // Comparable 필요
                ((Comparable<?>) key).compareTo(key);
            }
            root = new Entry<>(key, value, null);
            return null;
        }
        
        Entry<K, V> parent;
        int cmp;
        
        // 비교 방법 결정
        if (comparator != null) {
            // 외부 Comparator
            do {
                parent = t;
                cmp = comparator.compare(key, t.key);
                if (cmp < 0) t = t.left;
                else if (cmp > 0) t = t.right;
                else return t.setValue(value);
            } while (t != null);
        } else {
            // Comparable
            Comparable<? super K> k = (Comparable<? super K>) key;
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0) t = t.left;
                else if (cmp > 0) t = t.right;
                else return t.setValue(value);
            } while (t != null);
        }
        
        // 새 노드 + 균형 조정
        Entry<K, V> e = new Entry<>(key, value, parent);
        if (cmp < 0) parent.left = e;
        else parent.right = e;
        fixAfterInsertion(e);   // Red-Black 균형
        size++;
        return null;
    }
}

핵심:

  • compareTo 또는 Comparator 만 사용
  • Red-Black Tree (자가 균형 BST)
  • O(log n) 보장

3.3 비교 표

항목HashMapTreeMap
자료 구조해시 테이블Red-Black Tree
비교hashCode + equalscompareTo or Comparator
putO(1) 평균O(log n)
getO(1) 평균O(log n)
순서무순서키 정렬 순서
추가 메서드(없음)firstKey, lastKey, headMap, tailMap, subMap
null 키허용 (1개)보통 불가 (Comparator 가 처리 시 가능)
null 값허용허용

3.4 BigDecimal 키의 함정

// 같은 값, 다른 scale 의 BigDecimal
BigDecimal k1 = new BigDecimal("100.00");
BigDecimal k2 = new BigDecimal("100.0");

k1.equals(k2);         // false
k1.hashCode() == k2.hashCode();   // false
k1.compareTo(k2);      // 0 (값 같음)

// HashMap
Map<BigDecimal, String> hashMap = new HashMap<>();
hashMap.put(k1, "value1");
hashMap.get(k2);   // null! (equals 다름, 다른 버킷)
hashMap.size();    // 1

hashMap.put(k2, "value2");
hashMap.size();    // 2! (다른 키로 인식)

// TreeMap
Map<BigDecimal, String> treeMap = new TreeMap<>();
treeMap.put(k1, "value1");
treeMap.get(k2);   // "value1"! (compareTo 0, 같다고 봄)

treeMap.put(k2, "value2");
treeMap.size();    // 1! (덮어씀)

// → 완전히 다른 동작

3.5 LinkedHashMap — 삽입 순서 + 빠른 검색

// LinkedHashMap — HashMap + 양방향 LinkedList
public class LinkedHashMap<K, V> extends HashMap<K, V> {
    
    // Entry 가 추가로 before/after 포인터
    private LinkedHashMap.Entry<K, V> head;
    private LinkedHashMap.Entry<K, V> tail;
    
    // 삽입 순서 보장
    // 또는 접근 순서 (accessOrder = true 면)
}

// 사용
Map<String, Integer> ages = new LinkedHashMap<>();
ages.put("Charlie", 30);
ages.put("Alice", 25);
ages.put("Bob", 28);

// 순회 — 삽입 순서
for (Map.Entry<String, Integer> e : ages.entrySet()) {
    System.out.println(e.getKey());
}
// → Charlie, Alice, Bob (삽입 순서)

// 비교: HashMap 은 무순서
// 비교: TreeMap 은 정렬 순서 (Alice, Bob, Charlie)

3.6 사용 시점

HashMap 선택:
  ✓ 빠른 키-값 매핑
  ✓ 순서 무관
  ✓ 키가 equals + hashCode 정의

TreeMap 선택:
  ✓ 키 자동 정렬
  ✓ 범위 검색 (headMap, tailMap)
  ✓ 첫/마지막 키
  ✓ NavigableMap 메서드 (floorKey, ceilingKey 등)

LinkedHashMap 선택:
  ✓ 삽입 순서 보장
  ✓ LRU 캐시 구현
  ✓ HashMap 의 비결정 순서 해결

ConcurrentHashMap 선택:
  ✓ 멀티스레드
  ✓ 락 분할 (segment)

3.7 NavigableMap 메서드

// TreeMap 의 풍부한 메서드
TreeMap<Integer, String> map = new TreeMap<>();
map.put(1, "a");
map.put(3, "c");
map.put(5, "e");
map.put(7, "g");

// 범위
map.firstKey();        // 1
map.lastKey();         // 7
map.floorKey(4);       // 3 (≤ 4)
map.ceilingKey(4);     // 5 (≥ 4)
map.lowerKey(3);       // 1 (< 3)
map.higherKey(3);      // 5 (> 3)

map.headMap(5);        // {1=a, 3=c}
map.tailMap(5);        // {5=e, 7=g}
map.subMap(3, 7);      // {3=c, 5=e}

// 내림차순
NavigableMap<Integer, String> desc = map.descendingMap();
// → {7=g, 5=e, 3=c, 1=a}

3.8 ILIC 활용

public class ShipmentService {
    
    // 빠른 검색 — HashMap
    private final Map<Long, Shipment> cache = new HashMap<>();
    
    // 시간순 정렬 — TreeMap
    private final TreeMap<LocalDateTime, Shipment> timeline = new TreeMap<>();
    
    // 일별 통계 — TreeMap
    private final TreeMap<LocalDate, BigDecimal> dailySales = new TreeMap<>();
    
    public Shipment findById(Long id) {
        return cache.get(id);   // O(1)
    }
    
    public Map<LocalDate, BigDecimal> getSalesInRange(LocalDate from, LocalDate to) {
        return dailySales.subMap(from, true, to, true);   // 범위 검색
    }
    
    public LocalDateTime getLastUpdate() {
        return timeline.lastKey();   // 가장 최근
    }
}

3.9 자기 점검 답변

HashMap vs TreeMap 의 정밀한 차이는?

:
1. 비교 방식:

  • HashMap: hashCode + equals
  • TreeMap: compareTo 또는 Comparator
  1. 자료 구조:

    • HashMap: 해시 테이블
    • TreeMap: Red-Black Tree
  2. 시간 복잡도:

    • HashMap: O(1) 평균
    • TreeMap: O(log n)
  3. 순서:

    • HashMap: 무순서
    • TreeMap: 키 정렬
  4. 추가 기능:

    • TreeMap: NavigableMap 메서드 (firstKey, floorKey 등)
    • 범위 검색 (subMap 등)

빠른 검색은 HashMap, 정렬 + 범위는 TreeMap.


4️⃣ equals + hashCode + Comparable 일관성 종합

4.1 일관성의 3단계

1. equals + hashCode 일관성:
   x.equals(y) => x.hashCode() == y.hashCode()
   
2. equals + compareTo 일관성 (권장):
   x.compareTo(y) == 0 <=> x.equals(y)
   
3. Comparator + equals 일관성 (이상):
   compare(x, y) == 0 <=> x.equals(y)
   하지만 Comparator 는 다양한 기준이라 강제 X

4.2 일관성 깨졌을 때의 동작

public class Person implements Comparable<Person> {
    private String name;
    private int age;
    
    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof Person)) return false;
        Person p = (Person) obj;
        return name.equals(p.name) && age == p.age;
        // 이름 + 나이
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(name, age);   // equals 와 일치
    }
    
    @Override
    public int compareTo(Person other) {
        return Integer.compare(age, other.age);   // ❌ age 만
        // equals 와 불일치
    }
}

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

// HashSet (equals + hashCode 사용)
Set<Person> hashSet = new HashSet<>();
hashSet.add(p1);
hashSet.add(p2);
hashSet.size();   // 2 (다른 객체)

// TreeSet (compareTo 만 사용)
Set<Person> treeSet = new TreeSet<>();
treeSet.add(p1);
treeSet.add(p2);
treeSet.size();   // 1! (compareTo == 0)

// → 같은 두 객체가 다른 컬렉션에서 다른 크기
// 디버깅 매우 어려움

4.3 모든 컬렉션의 동작 영향

일관성 깨졌을 때 영향 받는 곳:

1. HashSet, HashMap (equals + hashCode)
   - 일관성 깨지면 같은 객체 못 찾음
   - put 후 get null

2. TreeSet, TreeMap (compareTo)
   - 일관성 깨지면 동등 객체 무시
   - 정렬 결과 예측 불가

3. ArrayList.contains, indexOf (equals)
   - 일관성과 무관

4. Collections.sort, Stream.sorted (compareTo/Comparator)
   - 결과만 보면 OK
   - 동등 객체의 순서 비결정

5. PriorityQueue (Comparable/Comparator)
   - 동등 객체의 순서 비결정
   - 일관성과 무관

6. Collections.binarySearch (compareTo/Comparator)
   - 일관성 깨지면 잘못된 인덱스

4.4 일관성 보장 패턴

// 패턴 1: 같은 필드 사용
public class Person implements Comparable<Person> {
    private String name;
    private int age;
    
    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof Person)) return false;
        Person p = (Person) obj;
        return age == p.age && Objects.equals(name, p.name);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
    
    @Override
    public int compareTo(Person other) {
        int cmp = Integer.compare(age, other.age);
        if (cmp != 0) return cmp;
        return name.compareTo(other.name);
        // age + name (equals 와 같은 필드)
    }
}

// 검증
Person p1 = new Person("Alice", 25);
Person p2 = new Person("Alice", 25);

p1.equals(p2);            // true
p1.hashCode() == p2.hashCode();   // true
p1.compareTo(p2);         // 0

// 모든 컬렉션에서 일관된 동작

4.5 record 의 자동 일관성

// Java 16+ record
public record Person(String name, int age) implements Comparable<Person> {
    
    // equals, hashCode 자동 생성 (모든 필드)
    
    // compareTo 만 직접
    @Override
    public int compareTo(Person other) {
        // 같은 필드 사용 권장
        int cmp = name.compareTo(other.name);
        if (cmp != 0) return cmp;
        return Integer.compare(age, other.age);
    }
}

// 자동 보장된 일관성 (모든 필드 기반)

4.6 BigDecimal 의 의도적 불일치

// BigDecimal 은 의도적 불일치
BigDecimal a = new BigDecimal("100.00");
BigDecimal b = new BigDecimal("100.0");

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

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

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

// 해결 — stripTrailingZeros
BigDecimal normalized = a.stripTrailingZeros();
// 정규화하여 사용

4.7 단위 테스트 검증

@Test
void testFullConsistency() {
    Person p1 = new Person("Alice", 25);
    Person p2 = new Person("Alice", 25);
    Person p3 = new Person("Bob", 30);
    
    // equals
    assertEquals(p1, p2);
    assertNotEquals(p1, p3);
    
    // hashCode (equals 일치)
    if (p1.equals(p2)) {
        assertEquals(p1.hashCode(), p2.hashCode());
    }
    
    // compareTo (equals 일치)
    assertEquals(0, p1.compareTo(p2));
    assertNotEquals(0, p1.compareTo(p3));
    
    if (p1.compareTo(p2) == 0) {
        assertEquals(p1, p2);
    }
    
    // 컬렉션 일관성
    Set<Person> hashSet = new HashSet<>(List.of(p1, p2, p3));
    Set<Person> treeSet = new TreeSet<>(List.of(p1, p2, p3));
    assertEquals(hashSet.size(), treeSet.size());   // 2 ✓
}

4.8 자기 점검 답변

equals/hashCode/compareTo 의 일관성이 깨지면?

:
1. HashSet/HashMap:

  • put 후 get null
  • contains 잘못 반환
  1. TreeSet/TreeMap:

    • 동등 객체 무시
    • size 다름
  2. 컬렉션 간 불일치:

    • HashSet.size != TreeSet.size
    • 디버깅 어려움
  3. 권장:

    • 같은 필드로 모두 구현
    • 단위 테스트 검증
    • record 활용 (자동)
  4. 의도적 예외:

    • BigDecimal: equals 와 compareTo 다름
    • 명확한 의도로만

5️⃣ Collections.sort, binarySearch 의 정밀

5.1 Collections.sort 의 내부

public static <T extends Comparable<? super T>> void sort(List<T> list) {
    list.sort(null);   // null = 자연 순서
}

public static <T> void sort(List<T> list, Comparator<? super T> c) {
    list.sort(c);
}

// List.sort 의 기본 구현
default void sort(Comparator<? super E> c) {
    Object[] a = this.toArray();
    Arrays.sort(a, (Comparator) c);
    
    ListIterator<E> i = this.listIterator();
    for (Object e : a) {
        i.next();
        i.set((E) e);
    }
}

핵심:

  • 배열로 변환
  • Arrays.sort 호출
  • 다시 List 에 반영

5.2 Arrays.sort 의 두 알고리즘

// 객체 배열 — TimSort
public static void sort(Object[] a) {
    if (LegacyMergeSort.userRequested)
        legacyMergeSort(a);
    else
        ComparableTimSort.sort(a, 0, a.length, null, 0, 0);
}

// primitive 배열 — Dual-Pivot QuickSort (int, long, double 등)
public static void sort(int[] a) {
    DualPivotQuicksort.sort(a, 0, 0, a.length);
}

핵심:

  • 객체 배열: TimSort (안정 정렬)
  • primitive 배열: Dual-Pivot QuickSort (불안정)

5.3 Collections.binarySearch

public static <T> int binarySearch(
        List<? extends Comparable<? super T>> list, T key) {
    return indexedBinarySearch(list, key);
}

public static <T> int binarySearch(
        List<? extends T> list, T key, Comparator<? super T> c) {
    if (c == null) return binarySearch(list, key);   // Comparable 활용
    return indexedBinarySearch(list, key, c);
}

private static <T> int indexedBinarySearch(
        List<? extends Comparable<? super T>> list, T key) {
    int low = 0;
    int high = list.size() - 1;
    
    while (low <= high) {
        int mid = (low + high) >>> 1;
        Comparable<? super T> midVal = list.get(mid);
        int cmp = midVal.compareTo(key);
        
        if (cmp < 0) low = mid + 1;
        else if (cmp > 0) high = mid - 1;
        else return mid;   // 발견
    }
    return -(low + 1);   // 음수 반환
}

특징:

  • 정렬된 List 필수
  • O(log n)
  • 발견: 인덱스
  • 없음: -(insertion point + 1)

5.4 binarySearch 의 결과 활용

List<Integer> sorted = Arrays.asList(1, 3, 5, 7, 9);

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

int notFound = Collections.binarySearch(sorted, 4);
// -3 (음수 = 없음)
// 절댓값 - 1 = 2 (4 가 삽입될 위치, sorted.get(2) 앞)

// 삽입 위치 계산
int insertionPoint = (notFound < 0) ? -(notFound + 1) : notFound;
// 2

// 정렬 유지하며 삽입
List<Integer> mutable = new ArrayList<>(sorted);
mutable.add(insertionPoint, 4);
// → [1, 3, 4, 5, 7, 9]

5.5 정렬되지 않은 List 의 위험

// 정렬 안 된 List
List<Integer> unsorted = Arrays.asList(5, 1, 9, 3, 7);

int idx = Collections.binarySearch(unsorted, 3);
// 결과 예측 불가!
// 이진 검색이 잘못된 방향으로 진행
// 정답이 있어도 못 찾을 수 있음

5.6 Stream.sorted

// Stream.sorted — 자연 순서
List<Integer> sorted = Stream.of(3, 1, 4, 1, 5)
    .sorted()
    .toList();

// Stream.sorted(Comparator)
List<Person> sorted = people.stream()
    .sorted(Comparator.comparing(Person::getAge))
    .toList();

// 내부: TimSort 변형 (또는 안정 정렬)

5.7 정렬의 안정성 (Stability)

안정 정렬 (Stable):
  동일한 키의 원래 순서 유지
  - TimSort (Collections.sort)
  - MergeSort

불안정 정렬 (Unstable):
  동일한 키의 순서 보장 X
  - QuickSort (Arrays.sort for primitives)
  - HeapSort

예시:
  [(1, "a"), (1, "b"), (2, "c")]
  
  안정 정렬 후 (키만 비교):
  [(1, "a"), (1, "b"), (2, "c")]   # a, b 순서 유지
  
  불안정 정렬 후:
  [(1, "b"), (1, "a"), (2, "c")]   # a, b 순서 바뀔 수 있음

5.8 객체 vs primitive 차이

// 객체 — 안정 (TimSort)
Person[] people = ...;
Arrays.sort(people, Comparator.comparing(Person::getAge));

// primitive — 불안정 (Dual-Pivot QuickSort)
int[] nums = {3, 1, 4, 1, 5};
Arrays.sort(nums);
// 안정성 보장 X (primitive 라 어차피 같으면 같음)

// 객체 List.sort 도 안정
List<Person> list = new ArrayList<>(...);
list.sort(Comparator.comparing(Person::getAge));

5.9 자기 점검 답변

Collections.sort 와 binarySearch 의 정밀 동작은?

:
1. Collections.sort:

  • 배열 변환 후 Arrays.sort
  • 객체: TimSort (안정)
  • O(n log n)
  1. binarySearch:

    • 정렬된 List 필수
    • O(log n)
    • 발견: 인덱스
    • 없음: -(insertion + 1)
  2. 안정 정렬 (TimSort):

    • 동일 키 순서 유지
    • 자바 표준 객체 정렬
  3. 불안정 정렬 (Dual-Pivot QuickSort):

    • primitive 배열
    • 어차피 같은 값 = 같음

6️⃣ PriorityQueue 의 동작 메커니즘

6.1 PriorityQueue 의 정의

public class PriorityQueue<E> extends AbstractQueue<E> {
    
    private Object[] queue;   // 이진 힙 배열
    private int size;
    private final Comparator<? super E> comparator;
    
    public PriorityQueue() {
        this(11, null);   // Comparable 사용
    }
    
    public PriorityQueue(Comparator<? super E> comparator) {
        this(11, comparator);
    }
    
    public PriorityQueue(int initialCapacity, Comparator<? super E> comparator) {
        this.queue = new Object[initialCapacity];
        this.comparator = comparator;
    }
}

핵심:

  • 이진 힙 (Binary Heap)
  • 최소 힙 (Min-Heap) 기본
  • Comparable 또는 Comparator 사용

6.2 이진 힙 구조

이진 힙의 특성:
  - 완전 이진 트리
  - 부모 ≤ 자식 (최소 힙)
  - 배열로 표현
  - 부모 인덱스 i 이면:
    - 왼쪽 자식: 2i + 1
    - 오른쪽 자식: 2i + 2
    - 부모: (i - 1) / 2

예: [1, 3, 5, 7, 4, 8, 9]

         1
        / \
       3   5
      / \ / \
     7  4 8  9

배열 인덱스: 0, 1, 2, 3, 4, 5, 6

6.3 offer/poll 의 동작

// offer — 추가 (sift-up)
public boolean offer(E e) {
    int i = size;
    size = i + 1;
    if (i == 0) {
        queue[0] = e;   // 첫 요소
    } else {
        siftUp(i, e);   // 부모와 비교, 작으면 교환
    }
    return true;
}

private void siftUp(int k, E x) {
    while (k > 0) {
        int parent = (k - 1) >>> 1;
        Object e = queue[parent];
        if (compare(x, (E) e) >= 0) break;   // 부모보다 크면 멈춤
        queue[k] = e;
        k = parent;
    }
    queue[k] = x;
}

// poll — 제거 (sift-down)
public E poll() {
    if (size == 0) return null;
    
    int s = --size;
    E result = (E) queue[0];        // 루트 = 최소값
    E x = (E) queue[s];              // 마지막 요소
    queue[s] = null;
    
    if (s != 0) {
        siftDown(0, x);   // 마지막을 루트에, 자식과 비교
    }
    return result;
}

private void siftDown(int k, E x) {
    int half = size >>> 1;
    while (k < half) {
        int child = (k << 1) + 1;
        Object c = queue[child];
        int right = child + 1;
        if (right < size && compare((E) c, (E) queue[right]) > 0) {
            c = queue[child = right];
        }
        if (compare(x, (E) c) <= 0) break;
        queue[k] = c;
        k = child;
    }
    queue[k] = x;
}

6.4 시간 복잡도

작업시간 복잡도
offer (add)O(log n) — sift-up
poll (remove)O(log n) — sift-down
peekO(1) — 루트
sizeO(1)
containsO(n) — 선형 검색
remove(item)O(n)

6.5 최소 힙 vs 최대 힙

// 기본 — 최소 힙
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
minHeap.offer(5);
minHeap.offer(2);
minHeap.offer(8);
minHeap.poll();   // 2 (최소)

// 최대 힙 — reverseOrder
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Comparator.reverseOrder());
maxHeap.offer(5);
maxHeap.offer(2);
maxHeap.offer(8);
maxHeap.poll();   // 8 (최대)

// 사용자 정의 비교
PriorityQueue<Shipment> urgentFirst = new PriorityQueue<>(
    Comparator.comparingInt(Shipment::getPriority).reversed()
);

6.6 PriorityQueue 의 활용

// 1. K 번째 큰 원소
public int findKthLargest(int[] nums, int k) {
    PriorityQueue<Integer> heap = new PriorityQueue<>();
    for (int n : nums) {
        heap.offer(n);
        if (heap.size() > k) heap.poll();
    }
    return heap.peek();
}

// 2. 다익스트라 알고리즘 (최단 경로)
public int dijkstra(Graph g, int source) {
    PriorityQueue<int[]> pq = new PriorityQueue<>(
        Comparator.comparingInt(a -> a[1])
    );
    pq.offer(new int[]{source, 0});
    // ...
}

// 3. ILIC — 긴급 출하 큐
PriorityQueue<Shipment> urgentQueue = new PriorityQueue<>(
    Comparator.comparingInt(Shipment::getPriority).reversed()
        .thenComparing(Shipment::getCreatedAt)
);

urgentQueue.offer(shipment1);
urgentQueue.offer(shipment2);

Shipment next = urgentQueue.poll();   // 가장 긴급한 것

6.7 PriorityQueue 의 순회

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

// ❌ 순회는 정렬 순서 보장 X
for (Integer n : queue) {
    System.out.println(n);
}
// → 1, 5, 3, 2 (힙 구조 그대로)

// ✓ 정렬된 순서로 꺼내기
while (!queue.isEmpty()) {
    System.out.println(queue.poll());
}
// → 1, 2, 3, 5

PriorityQueue 의 순회는 순서 보장 X. poll() 만 순서.

6.8 자기 점검 답변

PriorityQueue 의 동작 메커니즘은?

:
1. 자료 구조:

  • 이진 힙 (Binary Heap)
  • 배열로 표현
  • 완전 이진 트리
  1. 비교:

    • Comparable 기본
    • Comparator 도 가능
    • 기본 최소 힙
  2. 시간 복잡도:

    • offer/poll: O(log n)
    • peek: O(1)
    • contains: O(n)
  3. 활용:

    • K번째 원소 찾기
    • 다익스트라 알고리즘
    • 작업 스케줄러
  4. 주의:

    • 순회는 정렬 순서 X
    • poll() 만 정렬 순서

7️⃣ 정렬 알고리즘과 Comparator (Timsort, Dual-Pivot QuickSort)

7.1 정렬 알고리즘 비교

알고리즘평균최악공간안정성활용
QuickSortO(n log n)O(n²)O(log n)불안정기본
MergeSortO(n log n)O(n log n)O(n)안정객체 정렬
HeapSortO(n log n)O(n log n)O(1)불안정메모리 제약
TimSortO(n log n)O(n log n)O(n)안정Java 표준
Dual-Pivot QuickSortO(n log n)O(n²)O(log n)불안정primitive

7.2 TimSort (객체 정렬)

TimSort:
  - Tim Peters (Python 의 정렬 발명)
  - MergeSort + InsertionSort 결합
  - Java 7+ 의 객체 정렬 표준
  - 안정 정렬
  - 부분 정렬된 데이터에 매우 효율적

특징:
  - 작은 청크 → InsertionSort
  - 큰 청크 → MergeSort
  - "Run" 개념 (이미 정렬된 부분 활용)
  - 실세계 데이터에 빠름

7.3 Dual-Pivot QuickSort (primitive)

Dual-Pivot QuickSort:
  - 2개의 pivot 사용
  - Java 7+ primitive 배열 정렬
  - QuickSort 의 개선판
  - 불안정 정렬

알고리즘:
  - 2개 pivot p1, p2 (p1 < p2) 선택
  - 3개 구간으로 분할:
    - < p1
    - p1 ~ p2
    - > p2
  - 각 구간 재귀

장점:
  - 일반 QuickSort 보다 30% 빠름
  - primitive 배열에 효율적

7.4 Comparator 와 정렬 알고리즘

// TimSort 는 Comparator 활용
Person[] people = ...;
Arrays.sort(people, Comparator.comparing(Person::getAge));

// 내부:
// 1. TimSort 가 동작
// 2. 두 요소 비교 시 comparator.compare(p1, p2) 호출
// 3. 결과로 정렬

// 즉, Comparator 는 알고리즘의 비교 단계에 호출

7.5 안정성의 중요성

// 안정성이 필요한 시나리오

// 1. 다단계 정렬
List<Person> people = ...;

// 1차 정렬: 이름
people.sort(Comparator.comparing(Person::getName));

// 2차 정렬: 나이
people.sort(Comparator.comparing(Person::getAge));

// 결과:
// 안정 정렬 → 같은 나이 내에서 이름순 유지 ✓
// 불안정 정렬 → 같은 나이 내 이름순 깨질 수 있음 ✗

// 2. 이미 정렬된 부분 활용
// 부분 정렬된 데이터에 TimSort 가 매우 효율적

7.6 Stream.sorted 의 안정성

// Stream.sorted 도 안정 정렬
List<Person> sorted = people.stream()
    .sorted(Comparator.comparing(Person::getName))
    .sorted(Comparator.comparing(Person::getAge))
    .toList();
// 두 번 정렬해도 이름 순서 유지 (안정)

7.7 정렬 성능 측정

// 큰 데이터셋
List<Person> people = generateRandom(1_000_000);

// 일반 정렬
long start = System.nanoTime();
people.sort(Comparator.comparing(Person::getAge));
long t1 = System.nanoTime() - start;

// primitive 정렬 (int[] 추출 후)
int[] ages = people.stream().mapToInt(Person::getAge).toArray();
start = System.nanoTime();
Arrays.sort(ages);
long t2 = System.nanoTime() - start;

// t2 가 보통 더 빠름 (Dual-Pivot + autoboxing 회피)

7.8 ILIC 적용

public class ShipmentSortService {
    
    // 단순 정렬 — TimSort
    public List<Shipment> sortByCreatedAt(List<Shipment> shipments) {
        return shipments.stream()
            .sorted(Comparator.comparing(Shipment::getCreatedAt))
            .toList();
    }
    
    // 다단계 정렬 — TimSort 의 안정성 활용
    public List<Shipment> multiSort(List<Shipment> shipments) {
        return shipments.stream()
            .sorted(Comparator.comparing(Shipment::getCreatedAt))   // 2차
            .sorted(Comparator.comparing(Shipment::getPriority).reversed())   // 1차
            .toList();
        // 같은 priority 내에서 createdAt 순서 유지
    }
    
    // primitive 정렬 — Dual-Pivot QuickSort
    public int[] sortWeights(List<Shipment> shipments) {
        int[] weights = shipments.stream()
            .mapToInt(s -> s.getWeight().intValue())
            .toArray();
        Arrays.sort(weights);   // primitive 정렬
        return weights;
    }
}

7.9 자기 점검 답변

자바의 정렬 알고리즘과 Comparator 의 역할은?

:
1. TimSort (객체):

  • Java 7+ 표준
  • 안정 정렬
  • O(n log n)
  1. Dual-Pivot QuickSort (primitive):

    • Java 7+ 표준
    • 불안정
    • 일반 QuickSort 30% 빠름
  2. Comparator 의 역할:

    • 알고리즘의 비교 단계 제공
    • 안정성/시간복잡도와 무관
    • 비교 기준 결정
  3. 안정성의 활용:

    • 다단계 정렬
    • 같은 키의 원래 순서 유지

8️⃣ 실무 패턴 종합 + Phase 6 졸업 시험

8.1 실무 패턴 종합

패턴 1: Entity 의 비교

class Shipment implements Comparable<Shipment> {
    @Id Long id;
    String blNo;
    
    @Override
    public boolean equals(Object obj) {
        return id != null && id.equals(((Shipment) obj).id);
    }
    
    @Override
    public int hashCode() {
        return getClass().hashCode();
    }
    
    @Override
    public int compareTo(Shipment other) {
        return Long.compare(this.id, other.id);
    }
}

// 다양한 Comparator
class ShipmentComparators {
    public static final Comparator<Shipment> BY_WEIGHT = ...;
    public static final Comparator<Shipment> URGENT_FIRST = ...;
}
패턴 2: 값 객체 (Value Object) — record

public record Money(BigDecimal amount, String currency) 
        implements Comparable<Money> {
    
    @Override
    public int compareTo(Money other) {
        if (!currency.equals(other.currency)) {
            throw new IllegalArgumentException();
        }
        return amount.compareTo(other.amount);
    }
    // equals, hashCode 자동
}
패턴 3: 정렬 + 페이지네이션

public Page<Shipment> findShipments(int page, int size, String sortBy) {
    Comparator<Shipment> comparator = switch (sortBy) {
        case "weight" -> Comparator.comparing(Shipment::getWeight);
        case "createdAt" -> Comparator.comparing(Shipment::getCreatedAt);
        case "priority" -> Comparator.comparingInt(Shipment::getPriority).reversed();
        default -> Comparator.comparing(Shipment::getId);
    };
    
    List<Shipment> all = repository.findAll();
    all.sort(comparator);
    
    int from = page * size;
    int to = Math.min(from + size, all.size());
    return new Page<>(all.subList(from, to), all.size());
}
패턴 4: TreeMap 으로 시간 순 통계

TreeMap<LocalDate, BigDecimal> dailyTotal = shipments.stream()
    .collect(Collectors.groupingBy(
        s -> s.getCreatedAt().toLocalDate(),
        TreeMap::new,
        Collectors.reducing(
            BigDecimal.ZERO,
            Shipment::getFare,
            BigDecimal::add
        )
    ));

// 범위 조회
NavigableMap<LocalDate, BigDecimal> lastMonth = dailyTotal
    .tailMap(LocalDate.now().minusDays(30));
패턴 5: PriorityQueue 로 작업 스케줄러

class TaskScheduler {
    private PriorityQueue<Task> queue = new PriorityQueue<>(
        Comparator.comparingInt(Task::getPriority).reversed()
    );
    
    public void enqueue(Task task) {
        queue.offer(task);
    }
    
    public Task next() {
        return queue.poll();   // 가장 우선순위 높은 것
    }
}

8.2 Phase 6 졸업 시험 — 50 문항

equals/hashCode (10 문항)

Q1. == 와 equals 의 차이?
A1. 참조 vs 논리 비교

Q2. Object 의 기본 equals?
A2. this == obj (참조 비교)

Q3. equals 의 5가지 계약?
A3. 반사/대칭/추이/일관/null

Q4. hashCode 의 3가지 계약?
A4. 일관, equals 일치, 분포

Q5. equals override 시 왜 hashCode 도?
A5. HashMap/HashSet 동작 위해

Q6. JPA Entity equals 권장?
A6. id 비교 + null 처리

Q7. JPA hashCode 권장?
A7. getClass().hashCode()

Q8. record 의 자동 equals?
A8. 모든 필드 비교

Q9. 상속과 equals 문제?
A9. 추이성 깨짐 (Point/ColorPoint)

Q10. Objects.equals 효과?
A10. null 안전 비교

Comparable (10 문항)

Q11. Comparable<T> 정의?
A11. java.lang, 단일 메서드

Q12. compareTo 반환값?
A12. 음수/0/양수 (부호만)

Q13. 자연 순서?
A13. Comparable 이 정의하는 순서

Q14. compareTo 5가지 계약?
A14. 반사/대칭/추이/일관/equals 일치

Q15. equals 와 compareTo 일관성?
A15. compareTo == 0 ↔ equals == true (권장)

Q16. Integer.compare 이유?
A16. 오버플로우 안전

Q17. BigDecimal 의 예외?
A17. equals 와 compareTo 불일치

Q18. 다중 필드 비교?
A18. if 문 또는 thenComparing

Q19. Enum.compareTo?
A19. ordinal, final

Q20. Comparable 없으면 sort?
A20. ClassCastException

Comparator (10 문항)

Q21. Comparator<T> 정의?
A21. java.util, 함수형, 외부 비교

Q22. comparing 동작?
A22. 키 추출 + 자연 순서

Q23. thenComparing?
A23. 첫 0 이면 두 번째

Q24. reversed 동작?
A24. 역순 Comparator

Q25. naturalOrder vs reverseOrder?
A25. 자연 vs 역순 (정적)

Q26. nullsFirst/Last?
A26. null 처리

Q27. comparingInt 효과?
A27. autoboxing 회피

Q28. Stream.sorted?
A28. Comparator 활용 정렬

Q29. PriorityQueue 와 Comparator?
A29. 우선순위 결정

Q30. 람다 vs 익명 Comparator?
A30. 람다 권장

종합 (20 문항)

Q31. HashSet vs TreeSet 차이?
A31. equals+hashCode vs compareTo

Q32. HashMap vs TreeMap 차이?
A32. 같음 + Red-Black Tree

Q33. TreeSet 의 시간 복잡도?
A33. O(log n)

Q34. HashSet 의 시간 복잡도?
A34. O(1) 평균

Q35. PriorityQueue 자료 구조?
A35. 이진 힙

Q36. PriorityQueue offer 복잡도?
A36. O(log n)

Q37. TimSort?
A37. 객체 정렬, 안정, Java 표준

Q38. Dual-Pivot QuickSort?
A38. primitive 정렬, 불안정

Q39. binarySearch 결과 -3?
A39. 없음, 삽입 위치 = 2

Q40. NavigableMap 메서드?
A40. floorKey, ceilingKey, headMap, tailMap

Q41. equals + hashCode 깨지면?
A41. HashMap.get null

Q42. compareTo + equals 깨지면?
A42. TreeSet vs HashSet 크기 다름

Q43. record 의 자동 일관성?
A43. 모든 필드 기반

Q44. PriorityQueue 의 순회 순서?
A44. 보장 X (poll 만 정렬)

Q45. 안정 정렬의 의미?
A45. 같은 키의 순서 유지

Q46. 다단계 정렬?
A46. sort 여러 번 (안정 정렬)

Q47. parallelStream 정렬?
A47. 큰 데이터셋만 이점

Q48. LinkedHashMap?
A48. 삽입 순서 + HashMap

Q49. 비교 메커니즘 4가지?
A49. equals, hashCode, Comparable, Comparator

Q50. Phase 6 마스터 후?
A50. 자바 객체 비교 자유자재

8.3 채점

50 / 50 → Phase 6 마스터
45-49   → 거의 마스터
40-44   → 핵심 복습
< 40    → Unit 6.1 ~ 6.4 재학습

9️⃣ Phase 6 완주 + Phase 7 (I/O) 예고

9.1 Phase 6 학습 종합

Phase 6 — 객체 비교

Unit 6.1 — equals 와 hashCode 의 계약
  - == vs equals
  - 5+3 계약
  - JPA Entity 패턴
  - Lombok, record

Unit 6.2 — Comparable<T> 의 자연 순서
  - compareTo 의 의미
  - 자연 순서
  - 5가지 계약
  - 자바 표준 분석

Unit 6.3 — Comparator<T> 의 외부 비교
  - 함수형 인터페이스
  - comparing/thenComparing
  - reversed, naturalOrder
  - null 처리, primitive

Unit 6.4 — 비교의 종합 활용 (마스터)
  - 4가지 메커니즘 통합
  - HashSet vs TreeSet
  - HashMap vs TreeMap
  - 정렬 알고리즘
  - PriorityQueue

9.2 Phase 6 마스터 후 가능한 일

1. 객체 비교 자유자재
   - equals, hashCode, Comparable, Comparator
   - 일관성 보장
   - 적절한 도구 선택

2. 컬렉션 동작 정밀 이해
   - HashSet, TreeSet
   - HashMap, TreeMap, LinkedHashMap
   - PriorityQueue
   - 각각의 비교 메커니즘

3. 정렬 자유자재
   - 단순/다중 정렬
   - 안정/불안정 정렬
   - TimSort, Dual-Pivot QuickSort
   - Comparator 의 풍부한 활용

4. 면접 자신감
   - 모든 비교 질문 즉답
   - 컬렉션 동작 설명
   - 정렬 알고리즘

5. 실무 패턴
   - JPA Entity 비교
   - 정렬 + 페이지네이션
   - 작업 스케줄러 (PriorityQueue)
   - 시간순 통계 (TreeMap)

9.3 Phase 7 — I/O 시스템 큰 그림

Phase 7 — I/O 시스템 큰 그림

Unit 7.1 — try-with-resources
  → 자원 자동 해제
  → AutoCloseable 인터페이스

Unit 7.2 — NIO.2 Files, Path
  → 현대적 파일 API
  → Path, Files, Paths

Unit 7.3 — NIO Channel, Buffer
  → 채널 기반 I/O
  → Buffer, ByteBuffer

Unit 7.4 — Blocking vs Non-blocking (★ 마스터 깊이)
  → I/O 모델 정밀
  → Selector, NIO 의 본질

Unit 7.5 — 직렬화와 transient
  → ObjectInputStream/OutputStream
  → Serializable 인터페이스

9.4 Phase 6 → Phase 7 의 연결

Phase 6: 비교 (equals, hashCode, Comparable, Comparator)
   ↓
Phase 7: I/O (자원 처리, 파일, 네트워크)

연결:
  - I/O 의 자원은 비교 객체와 다름
  - 자원은 한정 + 닫아야
  - try-with-resources 가 AutoCloseable 활용
  - Blocking/Non-blocking 의 본질

9.5 3주차 누적 진행

✅ Phase 1 — Pass by Value (3 Unit)
✅ Phase 2 — 컬렉션 프레임워크 (6 Unit)
✅ Phase 3 — 해시의 원리 (4 Unit)
✅ Phase 4 — 추상화의 두 도구 (4 Unit)
✅ Phase 5 — 제네릭과 와일드카드 (5 Unit)
✅ Phase 6 — 객체 비교 (4 Unit)  ← 완주
🚀 Phase 7 — I/O 시스템 큰 그림 (다음)
⏭ Phase 8 — Stream 실전
⏭ Phase 9 — I/O 강화
⏭ Phase 10 — 함수형 프로그래밍

총: 26/43 Unit 작성 (Phase 6 완주, 약 60%)

🎯 핵심 요약 — 3줄 정리

1. 4가지 비교 메커니즘

  • equals, hashCode, Comparable, Comparator
  • 컬렉션마다 다른 메커니즘
  • 일관성 보장 필수

2. 컬렉션 동작의 정밀

  • HashSet/HashMap: hashCode → equals
  • TreeSet/TreeMap: compareTo or Comparator (equals 무시)
  • PriorityQueue: 이진 힙, O(log n)

3. 정렬 알고리즘

  • TimSort: 객체, 안정, Java 표준
  • Dual-Pivot QuickSort: primitive, 불안정
  • Comparator 가 비교 단계 제공

🏆 Phase 6 완주 — 객체 비교 마스터 달성

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

→ 자바 객체 비교 정복
→ 4가지 메커니즘 통합
→ 컬렉션 동작 깊이 이해
→ 정렬 알고리즘 + PriorityQueue 마스터

📚 다음으로...

Phase 7 — I/O 시스템 큰 그림

다음 Phase 는 자바 I/O 의 정복.

Phase 7 — I/O 시스템 큰 그림

Unit 7.1 — try-with-resources
  → AutoCloseable 인터페이스
  → 자원 자동 관리

Unit 7.2 — NIO.2 Files, Path
  → 현대적 파일 API
  → Path, Paths, Files

Unit 7.3 — NIO Channel, Buffer
  → 채널 기반 I/O
  → ByteBuffer, FileChannel

Unit 7.4 — Blocking vs Non-blocking (★ 마스터 깊이)
  → I/O 모델 정밀
  → Selector, NIO 의 본질
  → Reactive Programming 기초

Unit 7.5 — 직렬화와 transient
  → ObjectInputStream/OutputStream
  → Serializable
  → 보안 위험과 대안

Phase 6 와의 연결:

  • AutoCloseable 인터페이스 (Phase 4 의 인터페이스)
  • I/O 의 자원 관리
  • 비교는 객체 자체, I/O 는 자원

F-LAB JAVA · 3주차 · Phase 6 · Unit 6.4 · 끝
🏆 Phase 6 완주 — 객체 비교 마스터 달성

profile
Software Developer

0개의 댓글