728x90

자바 개발을 하다 보면 클래스를 설계할 때 equals()와 hashCode()를 재정의(Override)해야 한다는 이야기를 귀가 따갑게 듣습니다. 하지만 "왜 굳이 둘을 세트로 처리해야 하는가?"에 대해 명확한 논리를 가진 개발자는 의외로 많지 않습니다. 본 포스팅에서는 단순히 이론적인 설명을 넘어, 이 규칙을 어겼을 때 발생하는 실무적인 결함과 자바 콜렉션 프레임워크의 동작 원리를 심층적으로 분석합니다.
1. Equals와 HashCode의 역할 정의
자바의 모든 객체는 Object 클래스를 상속받으며, 기본적으로 두 메서드를 가지고 있습니다.
- equals(): 두 객체의 '논리적 동등성(Logical Equality)'을 비교합니다. 기본값은 주소값 비교(==)입니다.
- hashCode(): 객체를 식별하는 정수값(해시 코드)을 반환합니다. 주로 해시 기반 자료구조(HashSet, HashMap 등)에서 객체를 빠르게 찾기 위한 인덱스로 사용됩니다.
2. 왜 반드시 함께 재정의해야 하는가?
가장 큰 이유는 "equals()가 true인 두 객체의 hashCode() 값은 반드시 같아야 한다"는 자바의 일반 규약(General Contract) 때문입니다. 이를 어길 경우, HashMap이나 HashSet에 객체를 담았을 때 우리가 예상치 못한 오동작이 발생합니다.
| 논리적 비교 | 동일함 (True) | 동일함 (True) |
| 해시 자료구조 저장 | 서로 다른 버킷(Bucket)에 저장될 확률 높음 | 동일한 버킷에 저장됨 |
| 자료구조 내 조회 | 찾기 실패 (같은 데이터인데 없다고 나옴) | 정상적으로 찾기 성공 |
| 데이터 중복 방지 | 실패 (동일 데이터가 중복 저장됨) | 성공 (중복 제거) |
3. 해시 기반 콜렉션의 내부 동작 방식
HashMap에서 get(Object key)를 호출하면 다음 2단계를 거칩니다.
- hashCode() 확인: 저장된 객체들 중 같은 해시 코드를 가진 그룹(버킷)을 찾습니다.
- equals() 확인: 해당 그룹 내에서
equals()가 true인 객체를 최종적으로 선택합니다.
만약 equals()만 재정의하고 hashCode()를 방치하면, 논리적으로 같은 객체임에도 불구하고 1번 단계에서 서로 다른 해시 코드가 반환되어 아예 다른 버킷을 뒤지게 됩니다. 이것이 바로 "데이터 유실"처럼 보이는 논리 에러의 원인입니다.
4. Sample Example: 실무에서의 구현 방법
최근에는 직접 구현하기보다 IDE의 기능이나 Lombok, 또는 Java 14+의 Record를 사용하는 것이 안전합니다.
import java.util.Objects;
public class Member {
private String id;
private String name;
public Member(String id, String name) {
this.id = id;
this.name = name;
}
// equals 재정의: ID가 같으면 같은 회조로 인식
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Member member = (Member) o;
return Objects.equals(id, member.id);
}
// hashCode 재정의: equals에서 사용한 필드(id)를 반드시 포함해야 함
@Override
public int hashCode() {
return Objects.hash(id);
}
}
5. 핵심 요약 및 주의사항
- 불변성(Immutability): 가능하면
hashCode()계산에 사용되는 필드는final로 선언하여 값이 변하지 않게 하십시오. 객체가 저장된 후 필드값이 바뀌면 해시값도 변해버려 자료구조 내에서 길을 잃게 됩니다. - 일관성:
equals()비교에 사용되지 않는 필드는 절대로hashCode()계산에 포함하지 마십시오. - 성능: 너무 복잡한 해시 알고리즘은 성능을 저하시키므로
Objects.hash()같은 표준 유틸리티를 권장합니다.
참조 및 출처
- Oracle Java SE Documentation: Object Class (https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html)
- Effective Java 3rd Edition (Joshua Bloch 저) - Item 11: Always override hashCode when you override equals
- Java Performance: The Definitive Guide (Scott Oaks 저)
728x90
'Language > Java' 카테고리의 다른 글
| [JAVA] Checked Exception과 Unchecked Exception의 전략적 선택 기준 (0) | 2026.01.26 |
|---|---|
| [JAVA] Try-with-resources의 동작 원리와 AutoCloseable 인터페이스 : 완벽한 자원 해제 가이드 (0) | 2026.01.26 |
| [JAVA] JVM의 내부 구조 완벽 해부 : 메모리 관리의 핵심 원리 (0) | 2026.01.25 |
| [JAVA] 개발 환경 구성 시 환경 변수(JAVA_HOME, PATH)를 설정하는 이유는? (0) | 2026.01.25 |
| [JAVA] Big Decimal 클래스를 사용하는 이유는? 부동 소수점 오차 완벽 해결법 (0) | 2026.01.25 |