hashCode() 와 equals()

mongBrown·2026년 4월 19일

equals() 를 재정의했는데 HashMap 에서 값을 못 찾는 이유

Map<Point, String> map = new HashMap<>();
map.put(new Point(1, 2), "서울");

System.out.println(map.get(new Point(1, 2)));  // null

분명히 (1, 2) 로 넣었는데, (1, 2) 로 꺼내면 null 이 나온다. equals() 도 재정의했는데 왜 못 찾는 걸까.


HashMap 이 값을 찾는 방법

HashMap 은 모든 항목을 하나하나 비교하지 않는다. 그렇게 하면 항목이 많아질수록 탐색이 느려지기 때문이다.

대신 두 단계로 나눠서 처리한다.

1단계 — hashCode() 로 버킷을 찾는다

HashMap 은 내부적으로 배열로 나뉜 버킷을 가지고 있다. 키의 hashCode() 를 기준으로 어느 버킷에 저장할지 결정한다. 같은 hashCode 를 가진 항목들은 같은 버킷에 모인다. 탐색할 때도 hashCode 로 버킷을 먼저 좁혀서, 그 버킷 안에서만 비교한다.

2단계 — equals() 로 최종 확인한다

같은 버킷 안에는 hashCode 가 같은 여러 항목이 있을 수 있다. 서로 다른 값이라도 hashCode 가 같게 나오는 경우가 있는데, 이를 해시 충돌이라고 한다. equals() 는 버킷 안에서 실제로 같은 항목인지 최종적으로 가려내는 역할이다.

equals() 만 재정의하면 어떻게 되는가

hashCode() 를 재정의하지 않으면 Object 의 기본 구현이 사용된다. 기본 구현은 메모리 주소를 기반으로 hashCode 를 만든다.

Point p1 = new Point(1, 2);  // hashCode: 0x1a2b (메모리 주소 기반)
Point p2 = new Point(1, 2);  // hashCode: 0x5e6f (다른 메모리 주소)

p1p2 는 값은 같지만 new 로 만든 별개의 객체라 hashCode 가 다르다. HashMap 은 p1 을 넣을 때 버킷 A 에 저장하고, p2 로 꺼낼 때는 버킷 B 를 탐색한다. equals() 로 비교할 대상 자체를 찾지 못하는 것이다.

hashCode() 만 재정의하면 어떻게 되는가

이번엔 반대 케이스다. hashCode 를 재정의해서 같은 버킷은 찾았다.

Point p1 = new Point(1, 2);  // hashCode: 42 (재정의됨)
Point p2 = new Point(1, 2);  // hashCode: 42 (같은 버킷)

버킷은 맞게 찾았지만 equals() 기본 구현이 참조 비교라서, p1p2 는 다른 객체로 판단된다. 버킷 안에서 최종 확인 단계를 통과하지 못한다.

Java 가 요구하는 계약

이 동작 방식 때문에 Java 명세에는 다음 계약이 있다.

equals()truehashCode() 도 반드시 같아야 한다.

역방향은 성립하지 않아도 된다. hashCode 가 같아도 equals 는 false 일 수 있다 — 이게 해시 충돌이다.

이 계약을 지키려면 equals() 를 재정의할 때 hashCode() 도 반드시 같이 재정의해야 한다. 둘 중 하나만 바꾸면 HashMap, HashSet 같은 Hash 기반 자료구조가 정상적으로 동작하지 않는다.

profile
화이팅!

0개의 댓글