equals가 있는데 hashCode는 왜 필요할까?

2026. 1. 27. 16:33·Learning Log

Java 문법을 공부하다가 얼핏 비슷하면서도 서로 다른 개념들 때문에 정신이 혼미해졌다.

“equals는 값 비교잖아.”

“hashCode는… 객체 주소 비교?”

“그럼 identityHashCode는 뭐지?”

“HashMap에서 둘이 왜 같이 언급되지?”

처음엔 이 셋이 전부 비슷한 비교 도구라고 생각했다.

하지만,

👉 역할이 완전히 분리된 세 가지 도구였고

👉 특히 hashCode는 내가 처음 생각한 목적과 전혀 다른 이유로 존재하고 있었다.


1. 오해의 시작

처음엔 이렇게 생각했다.

hashCode는

→ 객체의 주소를 기반으로

→ 같은 객체인지 확인하는 용도다

❌ 이건 hashCode의 역할이 아니다.

✅ 이 역할에 더 가까운 건 System.identityHashCode()였다.


2. Java의 “같다”는 두 가지 의미가 있다

객체를 비교할 때, 사실 질문이 두 개다.

① 같은 객체인가? (정체성, identity)

- 메모리 상 같은 실물인가?
- ==
- System.identityHashCode()

② 값(의미)이 같은가? (논리적 동등성, equality)

- 상태나 내용이 같은가?
- equals()
- hashCode()

 

Java는 이 둘을 의도적으로 분리했다.


3. equals / hashCode / identityHashCode 비교

구분 equals hashCode identityHashCode
기준 값(의미) 값 기반 분류 객체 정체성
override 영향 받음 받음 무시
목적 논리적 동등성 빠른 탐색 객체 구분
사용처 값 비교 HashMap / HashSet 디버깅, 추적

👉 중요한 포인트는

hashCode는 “비교”가 아니라 “분류”를 위한 값이라는 것


4. 그런데 왜 equals만 쓰면 안 될까?

자연스럽게 이런 의문이 들었다.

“어차피 같은지 판단하는 건 equals인데

hashCode는 왜 필요하지?”

답은 성능, 그리고 HashMap의 구조에 있다.


5. HashMap의 구조

HashMap을 단순화 하면

[0] → 비어있음
[1] → 비어있음
[2] → (keyA, valueA)
[3] → (keyB, valueB) → (keyC, valueC)
[4] → 비어있음
  • [0] [1] [2] ... 는 bucket
  • 각 bucket에는 여러 key-value 쌍이 들어갈 수 있다

6. map.put(key, value) 내부에서 일어나는 일

① key.hashCode() 호출

int h = key.hashCode();

→ 숫자 하나를 얻는다.

② hashCode로 bucket 결정

h → 3번 bucket

③ 해당 bucket 확인

bucket이 비어 있다면

[3] → (key, value)

→ 바로 저장 (아주 빠름)

bucket에 이미 뭔가 있다면

[3] → (keyA, valueA)

→ 이제 equals() 등장

④ equals로 최종 판단

  • equals == true
    같은 키 → value 덮어쓰기
  • equals == false
    다른 키 → bucket에 추가

7. HashMap의 아주 중요한 전제

“같은 키라면, 같은 bucket에 있을 것이다.”

이 전제가 깨지면, HashMap은 정상 동작할 수 없다.


8. 여기서 equals / hashCode 규약이 등장한다

Java의 유명한 규칙:

equals가 true면 hashCode도 반드시 같아야 한다

왜 이 규칙이 필요할까?


❌ 규약을 어긴 경우를 생각해보자

User u1 = new User(1);
User u2 = new User(1);

u1.equals(u2); // true

하지만:

u1.hashCode() != u2.hashCode();

HashMap에 넣고 꺼내면?

map.put(u1, "data");
map.get(u2); // null

왜?

  • hashCode가 달라서
  • 다른 bucket을 봤고
  • equals 비교는 아예 시도조차 안 된다

9. 그래서 이 규약은 “논리적 필연”이다

HashMap의 동작 순서:

hashCode → bucket 선택
equals   → 같은 키인지 판정

즉, equals가 true인 두 객체는 반드시 같은 bucket에 있어야 한다.

그리고 bucket은?

👉 hashCode로 결정된다

그래서 결론은 하나다.

equals가 true라면 hashCode도 같아야 한다

이건 HashMap이 동작하기 위한 전제 조건이다.


10. 그럼 반대는 왜 허용될까?

a.hashCode() == b.hashCode(); // true
a.equals(b);                 // false

이건 허용된다.

  • 같은 bucket에 들어가도
  • bucket 안에서 equals로 구분하면 되기 때문

11. identityHashCode는 왜 따로 존재할까?

System.identityHashCode(obj)
  • equals나 hashCode의 구현과는 상관없이
  • 객체의 정체성 기준으로 동작한다
  • “이게 같은 객체인가?”를 구분하기 위한 태그

주 용도:

  • 디버깅
  • 객체 그래프 추적
  • 캐시 / 프록시 분석

최종 정리

  • == → 같은 객체인가?
  • equals() → 값이 같은가?
  • hashCode() → 값 기반 빠른 분류
  • identityHashCode() → 객체 기반 구분 태그

'Learning Log' 카테고리의 다른 글

[멋사 클라우드 5기] Day 6 - 자료구조, Lambda, Stream  (0) 2026.01.29
[멋사 클라우드 5기] Day 5 - Java 기본 라이브러리 & 컬렉션 정리  (0) 2026.01.28
abstract vs interface: 비슷한데 왜 굳이 둘 다 있을까?  (0) 2026.01.27
[멋사 클라우드 5기] Day 4 - 상속, 다형성, 그리고 abstract vs interface  (0) 2026.01.27
[멋사 클라우드 5기] Day 3 - 객체 생성 흐름과 클래스 구조 이해하기  (1) 2026.01.25
'Learning Log' 카테고리의 다른 글
  • [멋사 클라우드 5기] Day 6 - 자료구조, Lambda, Stream
  • [멋사 클라우드 5기] Day 5 - Java 기본 라이브러리 & 컬렉션 정리
  • abstract vs interface: 비슷한데 왜 굳이 둘 다 있을까?
  • [멋사 클라우드 5기] Day 4 - 상속, 다형성, 그리고 abstract vs interface
allluck777
allluck777
allluck777
    • 분류 전체보기 (44) N
      • AWS (0)
      • Network (0)
      • Linux (0)
      • Docker (0)
      • Project (4)
        • CloudNote (4)
      • Learning Log (36) N
      • Lecture (3)
        • 스프링 입문 - 코드로 배우는 스프링 부트, 웹 .. (3)
  • 전체
    오늘
    어제
  • hELLO· Designed By정상우.v4.10.6
allluck777
equals가 있는데 hashCode는 왜 필요할까?
상단으로

티스토리툴바