이펙티브 자바 완벽 공략 1부

아이템 11. equals를 재정의하려거든 hashCode도 재정의하라

아이템 11. equals를 재정의하려거든 hashCode도 재정의하라


equals를 재정의하려거든 hashCode도 재정의하라

equals()를 재정의했다면 반드시 함께 재정의해야 하는 메서드가 있다. 바로 hashCode()다.

이 둘은 항상 쌍으로 움직이는 계약(contract)이며, 하나라도 빠지면 컬렉션에서 심각한 문제가 발생한다.


hashCode 규약 정리

hashCode는 다음 3가지 규약을 반드시 만족해야 한다.


1. 동일한 객체는 항상 같은 hashCode를 가져야 한다

equals 비교에 사용되는 정보가 변하지 않았다면, hashCode는 항상 동일해야 한다

a.equals(b) == true
 a.hashCode() == b.hashCode()

👉 가장 중요한 규약


2. equals가 같으면 hashCode도 같아야 한다

두 객체가 equals로 같다면, 반드시 동일한 hashCode를 가져야 한다

이 규약을 어기면 HashMap, HashSet이 정상 동작하지 않는다


3. equals가 다르면 hashCode는 달라도 되고 같아도 된다

a.equals(b) == false
 a.hashCode() == b.hashCode() (가능)

👉 다를 필요는 없지만 👉 성능을 위해 다르게 만드는 것이 좋다


왜 equals와 hashCode는 같이 구현해야 하는가

이걸 이해하려면 HashMap의 동작 방식을 알아야 한다.


HashMap 동작 원리

1. 데이터 저장 (put)

  1. key의 hashCode() 호출
  2. 해시 값을 기반으로 버킷(bucket) 결정
  3. 해당 버킷에 데이터 저장

2. 데이터 조회 (get)

  1. key의 hashCode() 호출
  2. 같은 버킷 찾기
  3. 해당 버킷 내부에서 equals() 비교

👉 핵심

hashCode → 위치 찾기
equals   → 실제 비교

❌ hashCode를 구현하지 않은 경우

예제:

Map<PhoneNumber, String> map = new HashMap<>();

map.put(new PhoneNumber("123"), "Alice");
map.get(new PhoneNumber("123"));

문제 상황

  • equals는 같음 → true
  • hashCode는 다름

👉 결과

값을 못 찾는다 (null 반환)

왜 이런 일이 발생하는가

  • put할 때 → A 버킷에 저장
  • get할 때 → B 버킷에서 찾음

👉 애초에 다른 위치를 보고 있음

👉 equals가 호출될 기회조차 없음


❌ 모든 객체가 같은 hashCode를 반환하는 경우

@Override
public int hashCode() {
    return 42;
}

이 경우는?

  • 모든 객체가 같은 버킷에 저장됨

👉 해시 충돌(hash collision) 발생


해시 충돌 발생 시

  • 버킷 내부가 LinkedList로 변함
  • 모든 객체가 한 줄로 연결됨
Bucket 42:
[A] → [B] → [C] → [D] → ...

결과

  • 탐색 시간: O(1) → O(N)
  • HashMap → 그냥 LinkedList 수준

👉 성능 급격히 저하


핵심 포인트

hashCode는 “정확성”과 “성능”을 동시에 고려해야 한다


올바른 hashCode 구현 기준

1. equals에서 사용하는 모든 필드를 포함해야 한다

@Override
public int hashCode() {
    return Objects.hash(x, y);
}

👉 일부 필드 빠지면 문제 발생


2. 불변 필드를 사용하는 것이 좋다

  • 값이 바뀌면 hashCode도 바뀜
  • Map에서 위치가 달라짐

👉 매우 위험


equals와 hashCode 관계 정리

상황결과
equals true, hashCode 다름❌ 심각한 버그
equals false, hashCode 같음⚠️ 성능 저하
equals true, hashCode 같음✅ 정상

실무에서의 구현 방법


1. Lombok

@EqualsAndHashCode

👉 가장 많이 사용


2. Java Record

public record Point(int x, int y) {}

👉 equals + hashCode 자동 생성


3. IDE 자동 생성

  • IntelliJ Generate 기능

절대 하면 안 되는 것


❌ hashCode를 구현하지 않는 경우

👉 equals 재정의했다면 무조건 구현해야 한다


❌ 항상 같은 값 반환

return 1;

👉 HashMap 성능 완전 붕괴


❌ 일부 필드만 사용

👉 equals와 불일치 발생


핵심 요약

  • equals와 hashCode는 반드시 함께 구현해야 한다
  • equals가 같으면 hashCode도 같아야 한다
  • hashCode는 HashMap의 “위치 결정” 역할
  • 잘못 구현하면 데이터 조회 자체가 실패한다
  • 충돌이 많으면 성능이 O(1) → O(N)으로 떨어진다

한 줄 결론

equals는 “같은지 판단”이고, hashCode는 “어디에 있는지 찾는 키”다


*


© 2020. All rights reserved.

SIKSIK