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 |