Programming/Java

Java hashcode()에 대해

UNarD 2026. 3. 16. 14:42

☕ Java hashCode() 이해하기

hashCode()를 override 하라길래 하긴 했는데... 대체 왜 하는 건지 몰랐습니다.
파고들다 보니 생각보다 중요한 개념이어서 정리해 봤습니다 😅

💡 hashCode()를 제대로 이해하려면 해시 테이블을 먼저 알면 좋습니다.
👉 해시 테이블이란?


📦 hashCode()란?

hashCode()객체의 해시 코드 값을 반환하는 메서드입니다.
HashMap, HashSet 같은 해시 기반 컬렉션은 이 값을 사용해서 데이터를 저장할 위치를 결정합니다.

🗄️ 서랍장으로 비유해볼까요?

수만 개의 물건을 정리한다고 생각해볼게요.
아무 규칙 없이 넣으면 찾을 때 전부 뒤져야 하지만, 서랍 번호가 있다면 해당 서랍만 열면 됩니다.
hashCode()는 바로 "이 물건은 몇 번 서랍!"을 정해주는 번호 생성기입니다.

🔸 hashCode() 시그니처

public native int hashCode();
ℹ️ native 키워드란?

Java가 아닌 C/C++ 같은 네이티브 코드로 구현된 메서드라는 의미입니다.
Object.hashCode()는 JVM 내부에서 처리됩니다.

ℹ️ 해시 코드란?

해싱 알고리즘에 의해 생성된 32비트 정수 값입니다.
객체를 대표하는 일종의 지문 같은 개념으로, 해시 기반 컬렉션에서 위치를 결정하는 기준이 됩니다.


⚙️ hashCode()가 필요한 이유

아래 컬렉션들은 모두 해시 테이블 구조를 사용합니다.

HashMap / HashSet / Hashtable / LinkedHashMap

데이터를 저장할 때는 이런 순서로 동작합니다.

1️⃣ 키의 hashCode() 계산
2️⃣ index = hashCode & (n - 1) 공식으로 버킷(Bucket) 위치 계산
3️⃣ 해당 버킷에 데이터 저장

즉, hashCode()는 데이터가 어느 버킷에 저장될지 결정하는 기준 값입니다.

😱 hashCode()가 없다면?

hashCode()가 없다면 컬렉션은 모든 데이터를 하나씩 equals()로 비교해야 합니다.
데이터가 10,000개라면 최악의 경우 10,000번의 equals() 비교가 발생할 수도 있어요.

ℹ️ equals()는 느린 메서드일까?

equals() 자체가 느린 것은 아닙니다. 하지만 모든 요소를 순차 비교해야 하는 상황이 되면 성능이 크게 저하됩니다.


🧠 HashMap 내부 동작 간단 이해

HashMap에 데이터 저장
map.put("apple", 1);

1️⃣ hashCode 계산 : 키 "apple"의 해시 코드를 추출 (예: 93029210)
2️⃣ bucket 위치 계산 : hashCode & (n - 1) 공식으로 인덱스 도출 (예: 10번)
3️⃣ 데이터 저장 : bucket[10] 위치에 데이터를 저장

ℹ️ n은 무엇일까요?

n은 버킷 배열의 크기입니다. HashMap의 기본 초기 버킷 수는 16이며, 항상 2의 거듭제곱으로 유지됩니다.
항목 수가 용량 × 0.75(load factor)를 초과하면 버킷 배열이 2배로 확장됩니다.


🔗 equals()hashCode()의 관계

Java 공식 문서에 명시된 황금 규칙이 있습니다.

두 객체가 equals()에 의해 동일하다면,
두 객체의 hashCode() 값은 반드시 같아야 한다!

  • a.equals(b) == truea.hashCode() == b.hashCode() (필수!)
  • ⚠️ a.hashCode() == b.hashCode()equals()false일 수도 있음 → 이걸 해시 충돌이라고 해요!
💡 해시 충돌(Hash Collision)이란?

서로 다른 객체인데 hashCode() 값이 같은 경우를 말합니다.
해시 충돌이 발생하면 같은 버킷에 여러 데이터가 들어가고, 이때 equals()로 "진짜 내가 찾는 객체가 맞는지" 최종 확인을 합니다.


🛠 hashCode() 올바른 구현 방법

기본 Object 클래스의 hashCode()객체의 메모리 주소를 기반으로 값을 만듭니다.
내용이 같아도 주소가 다르면 다른 값이 나오기 때문에 반드시 Override가 필요합니다!

올바른 구현 (Java 7+)
import java.util.Objects;

class User {
    String name;
    int age;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof User)) return false;
        User user = (User) o;
        return age == user.age && Objects.equals(name, user.name);
    }

    @Override
    public int hashCode() {
        // ✅ 핵심: equals에서 비교하는 필드를 hashCode에서도 똑같이 사용!
        return Objects.hash(name, age);
    }
}

🚨 재정의하지 않으면 생기는 문제

HashSet에 똑같은 유저를 두 번 넣으면 어떻게 될까요?

HashSet 중복 테스트

Set<User> set = new HashSet<>();
set.add(new User("kim", 20));
set.add(new User("kim", 20));

System.out.println("Set size: " + set.size());
📤 출력 결과 (hashCode 미재정의 시)
Set size: 2

논리적으로는 같은 유저지만, hashCode가 다르니 서로 다른 버킷에 들어가 버립니다.
HashSet이 중복을 걸러낼 기회조차 잃게 되는 거예요 😵


📊 좋은 hashCode()의 조건

  1. 일관성 : 객체의 정보가 변하지 않았다면, 몇 번을 호출해도 같은 값이 나와야 합니다.
  2. 가변 키 주의 : 객체를 HashMap의 키로 넣은 뒤 필드 값을 바꾸면 안 됩니다. 해시 코드가 바뀌어 데이터를 못 찾게 돼요!
  3. 충돌 최소화 : 서로 다른 객체는 최대한 다른 값을 반환해야 성능이 좋아집니다.
⚠️ JVM 재실행 시 hashCode가 달라질 수 있어요

Java 공식 문서에 따르면, 같은 애플리케이션이라도 JVM을 다시 실행하면 hashCode 값이 달라질 수 있습니다.
hashCode 값을 파일이나 DB에 저장해서 재사용하는 건 위험합니다.


☕ 마무리

결국 hashCode()는 해시 기반 컬렉션에서 빠른 검색을 위한 색인입니다.
가장 중요한 포인트는 하나예요.

equals()를 재정의했다면, hashCode()도 무조건 세트로 재정의하자!

📚 참고자료

'Programming > Java' 카테고리의 다른 글

nextInt() 다음에 nextLine()이 씹히는 이유  (0) 2026.03.13
JAVA 문자열(String) 메서드 정리  (0) 2025.10.09
인터페이스 (Interface)  (0) 2025.05.14
추상 클래스(Abstract Class)  (0) 2025.05.14
Java의 조건문  (0) 2025.05.01