05.11 학습

한강섭·2025년 5월 11일
0

학습 & 숙제

목록 보기
85/103
post-thumbnail

사건의 발달

[PCCP 기출문제] 3번 / 충돌위험 찾기

이 문제를 풀던 도중 Point 클래스에 매개변수로 int r, int c, int sec 3가지가 있었는데 HashMap의 key로 Point를 사용하는 방법을 몰랐다.. (반성중)

이제부터라도 자세하게 공부해보자


해결방법

먼저 해결방법은 Point 클래스 내부에 equals 와 hashCode를 오버라이딩 하여 재정의 해주면 된다!

static class Point {
        int r;
        int c;
        int sec;
        
        public Point(int r, int c, int sec) {
            this.r = r;
            this.c = c;
            this.sec = sec;
        }
        
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            
            Point point = (Point) o;
            if (point.r == this.r && point.c == this.c && point.sec == this.sec) return true;
            else return false;
        }
        
        @Override
        public int hashCode() {
            return Objects.hash(r, c, sec);
        }
    }

여기 equals와 hashCode를 분석해보자


equals(Object o)

  1. 참조 동일성 검사 (this == o):

두 객체의 참조가 동일한지 확인한다(메모리 주소가 같은지)
자기 자신과 비교하면 항상 true

  1. null 체크 및 타입 검사:

o == null: 비교 대상이 null인지 확인한다. null과 비교하면 false를 반환
getClass() != o.getClass(): 두 객체의 클래스가 다르면 false를 반환
이는 상속 관계에서도 엄격하게 동일한 클래스만 비교한다 (LSP 원칙에 맞지 않을 수 있음)

LSP란?
SOLID 원칙 중 하나
서브타입(자식 클래스)는 언제나 기반타입(부모 클래스)로 교체할 수 있어야 한다
LSP를 지키기 위한 클래스 설계는 instanceof 사용! but 해시맵의 키로 사용할 때 더 안전하기 때문에 getClass 사용한다

  1. 타입 캐스팅:

비교 대상 객체를 Point 타입으로 캐스팅한다.
2번 단계에서 이미 타입을 확인했으므로 ClassCastException은 발생하지 않음

  1. 필드 비교:

모든 필드(r, c, sec)가 동일한지 비교한다. 여기는 로직에 맞게 수정가능!


hashCode()

Object.hash() 메서드를 사용해서 해시 코드 생성 후 반환

내부적으로 Arrays.hashCode() 를 사용하고 필드 순서가 달라지면 해시코드도 달라진다!


둘다 있어야 하는 이유

java의 모든 객체는 Object를 상속받고 있다! (equals, hashCode)

java의 equals-hashCode 계약

equals가 true인 두 객체는 같은 hashCode를 반환해야 한다!

HashMap에서 키를 찾을 때
1. 키의 hashCode()로 버킷 위치를 찾는다
2. 그 버킷 내에서 equals()로 정확한 키를 찾는다

equals() 만 오버라이딩 한 경우에는

Object의 기본 hashCode()는 객체의 메모리 주소를 기반으로 해시값을 생성하여
내용이 같은 두 Point 객체라도 다른 해시코드를 가진다. -> equals는 true여도 객체를 찾을 수 없음!

HashMap<Point, String> map = new HashMap<>();
Point p1 = new Point(1, 2, 3);
map.put(p1, "값");

Point p2 = new Point(1, 2, 3);  // p1과 내용이 같은 객체
System.out.println(p1.equals(p2));  // true
System.out.println(map.get(p2));    // null (찾을 수 없음)

hashCode() 만 오버라이딩 한 경우에는

Object의 기본 equals()는 참조 동일성(==) 만 비교한다
내용이 같아도 다른 객체는 equals()가 false를 반환하니깐 올바른 버킷을 찾을 수 있지만 키를 찾을 수 없다..

HashMap<Point, String> map = new HashMap<>();
Point p1 = new Point(1, 2, 3);
map.put(p1, "값");

Point p2 = new Point(1, 2, 3);  // p1과 내용이 같은 객체
System.out.println(p1.equals(p2));  // false (Object.equals는 참조 비교)
System.out.println(map.get(p2));    // null (찾을 수 없음)

해시 기반 컬렉션

HashMap, HashSet과 같은 해시 기반 컬렉션이 객체를 검색하는 과정을 살펴보면

  1. 버킷 위치 결정:
  • 키 객체의 hashCode()를 호출한다
  • 이 해시코드를 기반으로 내부 배열의 인덱스(버킷)를 계산한다
  1. 키 비교:
  • 계산된 버킷에서 객체를 순회하며 equals() 메서드로 키를 비교한다
  • 동일한 키를 찾으면 그에 연결된 값을 반환한다

hashCode -> 빠른 접근 경로 제공
equals -> 정확한 비교를 통한 선택

결론 : java의 equals-hashCode 계약에 맞게 항상 상황에 맞게 같이 맞춰줘야 한다!!


예고

hashCode 를 이런식으로 내용 동등성을 필요로 쓰는 경우가 대부분이고 참조 동등성이 필요한 이유가 생각이 나지 않았는데 메모이제이션과 캐싱, 락과 동기화, 객체 상태 변경 추적, 가비지 컬렉션 메커니즘 등등 많은 곳에서 쓰이는 것을 확인했고 추후에 더 깊게 포스팅 할 예정이다!

profile
기록하고 공유하는 개발자

0개의 댓글