Back-End (Web)/JAVA

[JAVA] HASH란 무엇인가

JABHACK 2024. 11. 21. 16:55

HASH

📌 입력 데이터를 고정된 크기의 값으로 변환하는 함수 또는 그 과정을 의미합니다

    • 다른 말로는 해시 값, 해시 코드, 체크섬 이라고 합니다.
    • key와 value 쌍으로 데이터를 저장하며, 키를 해시 함수를 통해 고유한 인덱스에 대응시켜 빠르게 값을 조회합니다.

 

  •  중복 허용을 안함, 순서 유지 안함, 빠른 검색, 삭제 가능
  • 해시 함수를 통해 시간복잡도가 1(반드시 1은 아닙니다. 충돌할 경우 달라지기도 합니다.) 한번에 정보의 위치를 찾을 수 있는 자료구조형태(해시 테이블)를 제작하는 과정
  • 꼭 주소로 구현할 필요는 없다 
  • 왼쪽의 index/data table의 경우 이차원 배열로 map은 아니다.
  • map으로 만들경우 메서드 차제적으로 for문으로 index값을 일일히 찾을 필요 없이 바로 index(key)를 찾을 수 있다.
  • 그래서 시간복잡도가 1으로 속도 개선에 큰 장점을 가지게 된다.
  • 위 그림은 엄밀히 말해, 에러난것, 키값이 중복되고 있다.
  • 2번째는 map이 반드시 주소를 이용해 구현되는 것은 아니지만 주소를 이용할 경우, 삭제가 편하다
  • 배열의 경우 삭제를 할 경우, 한번 복사를 하고 삭제를 하다보니 속도가 느리다. 다만 map의 경우 연결된 주소만 끊어버리면 삭제가 바로 진행되기에 삭제도 빠르다.
  • 여기서 중요한게, 오른쪽의 Map은 HASH MAP이라 불러야 한다. MAP은 자료구조

 

해시의 사용 사례

  1. 해시 테이블(Hash Table):
    • 키-값 쌍 데이터를 저장하고 검색할 때 사용. (예: Java의 HashMap, Python의 dict)
  2. 데이터베이스:
    • 데이터베이스에서 인덱스를 생성하거나 검색 속도를 높이기 위해 사용.
  3. 캐싱(Caching):
    • 자주 사용하는 데이터를 해시를 통해 빠르게 검색.
  4. 암호화 및 보안:
    • 비밀번호 저장, 데이터 무결성 검증 등에 사용. (예: SHA-256, MD5)
  5. 파일 검색:
    • 파일의 고유한 해시 값을 이용하여 중복 파일을 찾거나 무결성을 확인.

장점

  1. 빠른 검색 속도 (O(1))
    • 해시 테이블을 사용하면 데이터를 저장하거나 검색하는 데 평균적으로 **O(1)**의 시간 복잡도를 가짐.
      예: 키를 기반으로 바로 접근하므로 순차 탐색이 필요 없음.
  2. 효율적인 데이터 저장 및 관리
    • 다양한 크기의 데이터(문자열, 숫자 등)를 간단한 키-값 구조로 저장할 수 있음.
    • 해시 함수를 통해 고정된 크기의 배열에 데이터를 저장해 메모리 사용을 최적화함.
  3. 충돌 해결 기법의 다양성
    • 체이닝(Chaining), 오픈 어드레싱(Open Addressing) 등의 충돌 해결 기법으로 충돌 발생 시에도 효율적으로 처리 가능.
  4. 유연한 데이터 처리
    • 문자열, 숫자, 객체 등 다양한 유형의 데이터를 처리할 수 있어 범용적임.
      예: Java의 HashMap, Python의 dict처럼 키-값 쌍을 쉽게 저장하고 검색.
  5. 확장성
    • 데이터베이스, 캐싱 시스템, 파일 검색 등에서 널리 사용되며, 다양한 응용 가능.
  6. 보안 기능
    • 해시 알고리즘(SHA-256, MD5 등)은 암호화, 데이터 무결성 검증 등에 사용됨.

단점

  1. 충돌(Collision) 문제
    • 서로 다른 키가 같은 해시 값을 가질 경우 성능이 저하될 수 있음.
    • 충돌 해결 기법을 적용해야 하며, 이로 인해 성능이 떨어질 수 있음.
      • 예: 체이닝 사용 시, 연결 리스트를 순차적으로 탐색해야 하는 경우 발생.
  2. 메모리 낭비 가능성
    • 해시 테이블은 초기 크기를 고정하거나 동적으로 조정하는데, 너무 큰 크기를 할당하면 메모리 낭비가 발생.
    • 너무 작은 크기일 경우 충돌이 잦아져 성능 저하 가능.
  3. 정렬되지 않은 데이터
    • 해시 테이블은 키-값을 저장하는 구조이므로 데이터가 정렬되지 않음.
    • 정렬된 데이터가 필요하면 별도의 작업 필요.
  4. 해시 함수 설계의 중요성
    • 해시 함수가 데이터를 균등하게 분배하지 못하면 충돌이 증가해 성능이 저하될 수 있음.
      • 예: 키 값이 편향된 데이터(연속된 숫자 등)를 처리할 때.
  5. 해시 함수의 연산 비용
    • 복잡한 해시 함수는 연산 비용이 증가할 수 있어 작은 데이터에서는 오히려 비효율적.
  6. 재해시(Rehashing) 비용
    • 테이블이 가득 차거나 특정 임계치를 초과할 경우 크기를 늘려야 하며, 이 과정에서 모든 데이터를 다시 해싱해야 함.
    • 이는 큰 데이터셋에서 성능에 영향을 미칠 수 있음.
  7. 데이터 삭제 시 문제
    • 오픈 어드레싱을 사용하는 경우, 데이터를 삭제하면 그 자리의 값이 비워지면서 연결된 데이터 검색에 영향을 줄 수 있음.

 

 

해시 함수 ( Hash Function )

📌 입력받은 데이터를 해시 값으로 출력시키는 알고리즘

  • 알고리즘의 형태는 다양합니다. 목적에 맞게 다양하게 설계되고 유용하게 사용됩니다.
  • 자료구조, 캐시, 검색, 에러 검출, 함도 등 유용하게 많이 사용됩니다.
// 해시 함수 예시
Integer hashFunction(String key) {
	
    return (int)(key.charAt(0)) % 10;
}

 

  • 위의 그림을 코드로 만든 것, 이 경우 위 그림처럼 return으로 key값을 반환해 준다.

해시 함수의 특징

  • 일관성: 동일한 입력값에 대해 항상 동일한 해시 값을 반환.
  • 고른 분배: 입력값이 다양해도 출력값이 균일하게 분포.
  • 빠른 계산: 빠른 시간 안에 값을 계산.
  • 충돌 최소화: 서로 다른 입력값이 동일한 해시 값을 가지는 확률을 낮춤.

 

 

해시 테이블 ( Hash Table )

📌 키와 값을 함께 저장해 둔 데이터 구조

  • 아래와 같이 행, 열로 구성된 표에 저장되는 것과 유사합니다.
  • 다만 테이블에 데이터를 저장할 때 순서는 무작위로 지정되어 저장됩니다.
  • 그로인해 중간에 여유 공간이 발생할 수 있습니다.
  • 해시 테이블은 map 형태의 자료구조입니다.

 

  • 하나의 주소를 갖는 파일 영역을 Bucket, 한개의 레코드를 저장할 수 있는 공간을 Slot이라 말합니다.

 

 

해싱 ( Hashing )

📌 데이터를 해시 함수를 이용해 고유한 *해시 값(Hash Value)*으로 변환하는 과정

  • 변환된 해시 값은 위 예시처럼 데이터의 저장 위치를 결정하거나, 데이터 검색 시 활용됩니다.
  • 해싱을 잘못해서 key가 중복되면 충돌이 발생할 수 있습니다.

 

 

충돌 (Collision)

📌 해시 함수로 계산된 key값이 곂치는 경우, 동일한 값을 인덱스에 저장하려 할 때 발생합니다.

 

왜 충돌이 발생하는가?

  1. 해시 테이블 크기의 한계:
    • 해시 값의 범위(테이블 크기)가 유한하기 때문에, 입력값(키)이 다양해도 모든 값을 고유한 해시 값으로 매핑할 수 없음.
    • 예: 테이블 크기가 10이면, hash(21) = 1, hash(11) = 1처럼 서로 다른 키가 동일한 인덱스에 매핑될 수 있음.
  2. 해시 함수의 설계 문제:
    • 해시 함수가 키를 균일하게 분산하지 못하거나 특정 키 값 패턴에 대해 편향된 결과를 생성하면 충돌 발생 가능성이 증가.

충돌 해결 방법

1. 체이닝 (Chaining)

  • 방식: 충돌이 발생한 동일한 해시 값의 데이터를 연결 리스트(Linked List) 형태로 저장.
  • 동작 과정:
    • 충돌 시, 같은 인덱스에 새로운 데이터를 리스트의 형태로 추가.
    • 데이터를 검색할 때 해당 인덱스의 리스트를 순회하며 원하는 데이터를 찾음.
  • 장점:
    • 테이블 크기를 초과하는 데이터도 저장 가능.
    • 테이블의 크기를 크게 증가시키지 않아도 됨.
  • 단점:
    • 충돌이 많아지면 리스트가 길어져 검색 시간이 O(n)으로 증가.

 

2. 오픈 어드레싱 (Open Addressing)

  • 방식: 충돌이 발생하면 다른 빈 슬롯(인덱스)을 찾아 데이터를 저장.
  • 기법:
    1. 선형 탐사 (Linear Probing):
      • 충돌이 발생하면 고정된 간격(보통 1)을 더해 다음 인덱스를 탐색.
      • 예: hash(21) = 1에서 충돌 시, index = 2, index = 3 탐색.
      장점: 구현이 간단.
      단점: 충돌이 많아지면 클러스터링(연속된 충돌) 발생 가능.
    2. 이차 탐사 (Quadratic Probing):
      • 충돌 시 인덱스 이동 간격을 제곱 형태로 증가.
      • 예: index = 1, index = 1 + 1^2, index = 1 + 2^2.
      장점: 클러스터링 문제를 어느 정도 해결.
      단점: 테이블의 크기가 소수(Prime)여야 효율적.
    3. 이중 해싱 (Double Hashing):
      • 충돌 시 다른 해시 함수를 사용해 새로운 위치를 계산.
      • 예: hash1(21) = 1, 충돌 발생 시 hash2(21) = 3.
      장점: 충돌 분산 효과가 높음.
      단점: 해시 함수 설계가 복잡.


충돌 해결 기법의 선택

  • 데이터의 크기와 성격에 따라 충돌 해결 기법이 달라짐.
    • 데이터가 많고 빈도가 높은 경우 → 체이닝 적합.
    • 메모리가 제한적일 경우 → 오픈 어드레싱 적합.

충돌 해결 방법 비교

 

 

 

해시셋 ( Hashset )

📌 Set 인터페이스를 구현한 클래스 중 하나로, 데이터 중복, 순서를 유지하지 않는 집합입니다.

  • HashMap, HashSet 등 해시 기반 컬렉션입니다.
  • 컬렉션 = java에서 데이터를 효율적으로 저장하고 조작하기 위한 객체들의 그룹(container)을 말합니다.

 

HashSet의 작동 원리

HashSet은 내부적으로 HashMap을 사용해 데이터를 저장합니다.
데이터를 저장할 때, 키(key)와 값(value) 중에서 값(value)은 더미 객체로 설정되고, 키(key) 저장됩니다.

  1. 저장: add(E e) 메서드를 호출하면, 입력 데이터의 해시 코드(hashCode)를 계산해 해시 테이블의 위치를 결정.
  2. 중복 확인: equals() 메서드를 사용해 중복 여부를 판단.
    • 같은 해시 코드를 가진 데이터라도 equals() 메서드가 true를 반환하면 중복으로 간주.
import java.util.HashSet;

public class HashSetExample {
    public static void main(String[] args) {
        // HashSet 선언
        HashSet<String> set = new HashSet<>();

        // 데이터 추가
        set.add("Apple");
        set.add("Banana");
        set.add("Cherry");

        // 중복 추가 시 무시
        set.add("Apple");

        // 출력
        System.out.println(set);  // 출력: [Banana, Apple, Cherry] (순서가 랜덤)

        // 특정 값 포함 여부 확인
        System.out.println(set.contains("Banana"));  // true

        // 데이터 삭제
        set.remove("Cherry");

        // 크기 확인
        System.out.println(set.size());  // 2
    }
}

 

HashSet의 주요 메서드

  • set의 주요 메서드를 계승하고 있습니다.

 

HashSet과 다른 Set 구현체 비교

 

 

Set 인터페이

 🐳 중복을 허용하지 않는 집합(Collection)을 나타내는 인터페이스입니다.

 

Set의 주요 특징

  1. 중복 허용 안 함
    • 동일한 요소를 여러 번 저장할 수 없습니다.
    • 예: {1, 2, 2, 3} -> {1, 2, 3}
  2. 순서 유지 안 함
    • 데이터의 저장 순서를 보장하지 않습니다.
    • 일부 구현체(LinkedHashSet)는 예외적으로 삽입 순서를 유지.
  3. null 값
    • null 값을 허용하지만, 한 번만 저장 가능.
  4. 기본 연산 지원
    • 집합 연산을 쉽게 구현할 수 있습니다(교집합, 합집합, 차집합).

 

 

해시맵 ( Hashmap )

📌 Map 인터페이스의 구현체로, 키(Key)와 값(Value)의 쌍으로 데이터를 저장합니다.

  • 각각의 키는 고유하며, 각 키에 대해 값을 매핑합니다. HashMap은 해시 테이블을 사용하여 데이터를 빠르게 검색하고 관리할 수 있도록 합니다.
  • HashMap, HashSet 등 해시 기반 컬렉션입니다.
  • 컬렉션 = java에서 데이터를 효율적으로 저장하고 조작하기 위한 객체들의 그룹(container)을 말합니다.

 

HashMap의 주요 특징

  1. 키-값 쌍(Key-Value Pair)
    • HashMap은 데이터를 **키(Key)**와 **값(Value)**의 쌍으로 저장합니다.
    • 키는 중복될 수 없고, 값은 중복될 수 있습니다.
    • 예: map.put("Alice", 25)에서 "Alice"는 키, 25는 값입니다.
  2. 중복 키를 허용하지 않음
    • 동일한 키로 여러 번 값을 넣으면, 기존의 값이 새로운 값으로 덮어씌워집니다.
    • 예: map.put("Alice", 25) 이후 map.put("Alice", 30)을 호출하면 "Alice"의 값은 30으로 변경됩니다.
  3. 순서 보장하지 않음
    • HashMap은 저장된 데이터를 순서대로 유지하지 않습니다.
      순서가 중요한 경우, LinkedHashMap을 사용할 수 있습니다.
  4. 빠른 검색 속도
    • HashMap은 해시 테이블을 사용하여 **O(1)**의 평균 시간 복잡도로 검색, 삽입, 삭제가 가능합니다.
    • 해시 충돌을 처리하는 방법이 있기 때문에 최악의 경우에도 성능이 크게 저하되지는 않습니다.
  5. null 값 허용
    • HashMap은 한 개의 null 키여러 개의 null 값을 허용합니다.
    • 예: map.put(null, "some value") 또는 map.put("some key", null)이 가능합니다.

 

import java.util.HashMap;

public class HashMapExample {
    public static void main(String[] args) {
        // HashMap 생성
        HashMap<String, Integer> map = new HashMap<>();

        // 데이터 추가
        map.put("Alice", 25);  // "Alice" 키에 25 값 저장
        map.put("Bob", 30);    // "Bob" 키에 30 값 저장
        map.put("Charlie", 35); // "Charlie" 키에 35 값 저장

        // 값 출력
        System.out.println("Alice's age: " + map.get("Alice")); // 출력: Alice's age: 25

        // 존재 여부 확인
        System.out.println("Contains 'Bob'? " + map.containsKey("Bob")); // 출력: Contains 'Bob'? true

        // 모든 키와 값 출력
        for (String key : map.keySet()) {
            System.out.println(key + ": " + map.get(key));
        }
        // 출력:
        // Alice: 25
        // Bob: 30
        // Charlie: 35

        // 키 삭제
        map.remove("Bob");  // "Bob" 삭제
        System.out.println("Contains 'Bob' after removal? " + map.containsKey("Bob")); // 출력: false
    }
}

 

HashMap의 성능

  • 검색, 삽입, 삭제: 평균적으로 O(1)의 시간 복잡도를 가집니다.
    • 해시 테이블을 사용하기 때문에, 키를 해시 함수로 변환하여 빠르게 위치를 찾습니다.
  • 충돌 처리: 여러 항목이 동일한 해시 값을 갖는 경우 체이닝(Chaining) 또는 오픈 어드레싱(Open Addressing) 방식으로 충돌을 처리합니다.
  • 최악의 경우: 해시 충돌이 많아지면, 검색/삽입/삭제가 O(n)으로 성능이 저하될 수 있습니다. 이를 방지하기 위해 적절한 해시 함수충돌 처리 전략이 중요합니다.

 


 

HashMap의 장점과 단점

장점

  1. 빠른 데이터 검색: 해시 테이블을 사용하여 평균적으로 O(1)의 시간 복잡도로 데이터를 빠르게 조회, 삽입, 삭제할 수 있습니다.
  2. 키를 통한 데이터 접근: 키를 사용하여 데이터를 빠르고 효율적으로 관리할 수 있습니다.
  3. null 허용: null 값을 키와 값에 사용할 수 있어 유연성 제공.

단점

  1. 순서 보장 없음: HashMap은 순서가 보장되지 않으므로, 데이터가 저장된 순서를 유지하려면 LinkedHashMap을 사용해야 합니다.
  2. 메모리 사용량: 해시 테이블은 추가 메모리를 사용하므로, 작은 데이터 세트에서는 오히려 비효율적일 수 있습니다.
  3. 충돌 처리 필요: 해시 충돌이 발생하면 성능이 저하될 수 있으므로, 충돌 처리 방법에 대한 고려가 필요합니다.

 


 

HashMap의 주요 메서드

 

 

HashCode( ) 메서드

📌Object 클래스에 정의된 메서드로, 객체를 식별하기 위한 정수 값(해시 코드)을 반환합니다. 이 값은 객체를 빠르게 검색하거나 비교할 때 사용됩니다.

 

hashCode()의 특징

  1. 정수 값 반환
    • 객체의 해시 코드를 나타내는 정수를 반환.
    • 예: 123456, -987654, 등.
  2. 같은 객체는 같은 해시 코드
    • 동일한 객체(equals() 메서드로 비교 시 true)라면 동일한 hashCode를 반환해야 함.
    • 하지만, 서로 다른 객체가 같은 해시 코드를 가질 수도 있음(해시 충돌).
  3. 기본 구현
    • Object 클래스에서 기본적으로 정의되어 있으며, 객체의 메모리 주소를 기반으로 해시 코드를 생성.
  4. 재정의 가능
    • 개발자가 특정 로직에 따라 hashCode()를 재정의 가능. 주로 equals() 메서드와 함께 재정의.
public class HashCodeExample {
    public static void main(String[] args) {
        Object obj1 = new Object();
        Object obj2 = new Object();

        System.out.println(obj1.hashCode()); // 출력: (예) 366712642
        System.out.println(obj2.hashCode()); // 출력: (예) 1829164700
    }
}
  • obj1과 obj2는 서로 다른 객체이므로 해시 코드가 다름.

 

hashCode()와 equals()의 관계

  1. 규칙:
    • equals()가 true인 두 객체는 항상 같은 hashCode() 값을 가져야 함.
    • 그러나, 같은 hashCode() 값을 가진 객체가 **반드시 equals()가 true**는 아님.
  2. 의미:
    • hashCode()는 빠른 검색을 위한 값이고,
    • equals()는 객체의 논리적 동등성을 비교하는 메서드.

재정의된 hashCode()와 equals()

Java에서 hashCode()와 equals()는 함께 재정의해야 합니다.
HashSet이나 HashMap 같은 컬렉션은 hashCode()와 equals()의 일관성을 가정하여 동작합니다.

 

 

< 예제: 커스텀 클래스에서 hashCode()와 equals() 재정의 >

import java.util.Objects;

class Person {
    String name;
    int age;

    // 생성자
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // hashCode() 재정의
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    // equals() 재정의
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Person person = (Person) obj;
        return age == person.age && Objects.equals(name, person.name);
    }
}

public class HashCodeEqualsExample {
    public static void main(String[] args) {
        Person p1 = new Person("Alice", 25);
        Person p2 = new Person("Alice", 25);

        // 해시 코드가 동일함
        System.out.println(p1.hashCode() == p2.hashCode()); // true

        // equals() 비교
        System.out.println(p1.equals(p2)); // true
    }
}

실행 결과:

  • p1과 p2는 같은 name과 age를 가지므로 equals()와 hashCode() 모두 동일하게 동작.

 


hashCode()와 컬렉션

  • HashSet, HashMap 등은 hashCode()를 사용해 데이터를 관리.
  • 데이터를 추가할 때, 먼저 hashCode() 값으로 버킷(bucket)을 결정하고,
    동일한 버킷에 여러 데이터가 들어올 경우 **equals()**로 중복 여부를 확인.

< 예제: HashSet에서 hashCode()와 equals() >

import java.util.HashSet;

class Student {
    int id;
    String name;

    public Student(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public int hashCode() {
        return id; // ID를 기반으로 해시 코드 생성
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Student student = (Student) obj;
        return id == student.id && name.equals(student.name);
    }
}

public class HashSetExample {
    public static void main(String[] args) {
        HashSet<Student> set = new HashSet<>();

        set.add(new Student(1, "Alice"));
        set.add(new Student(1, "Alice")); // 중복 데이터
        set.add(new Student(2, "Bob"));

        System.out.println(set.size()); // 출력: 2
    }
}

 


hashCode() 사용 시 주의점

  1. equals와 일관성 유지
    • equals()가 true면 hashCode()도 같아야 함.
  2. 해시 충돌
    • 서로 다른 객체가 같은 hashCode()를 가질 경우, 충돌 발생.
    • 충돌이 많아지면 해시 기반 자료구조의 성능 저하.
  3. 재정의 필요성
    • 사용자 정의 클래스에서는 반드시 hashCode()와 equals()를 함께 재정의해야 올바르게 동작.

 

 

equals메서드 정의시 항상 hashcode()를 함께 정의해야하는 이유

 

Java의 기본 규약

Java에서 equals()와 hashCode()의 관계를 다음과 같이 규정합니다:

  1. equals()가 true를 반환하는 두 객체는 반드시 같은 hashCode()를 가져야 한다.
    • 동일한 객체를 나타내므로 동일한 해시코드가 필요.
  2. hashCode()가 같다고 해서 equals()가 반드시 true는 아니다.
    • 다른 객체들이 해시 충돌을 일으킬 수 있음.

 

함께 정의하지 않으면 발생하는 문제

1. HashMap과 HashSet에서 데이터 검색 실패

  • HashMap과 HashSet은 내부적으로 객체를 저장할 때 먼저 hashCode()로 버킷(bucket)을 찾고, 이후 equals()로 동등성을 확인합니다.
  • 만약 equals()와 hashCode()가 일관되지 않다면, 검색, 삽입, 삭제 작업이 제대로 동작하지 않습니다.
import java.util.HashSet;

class Person {
    String name;

    public Person(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Person person = (Person) obj;
        return name.equals(person.name);
    }

    // hashCode()를 정의하지 않음
}

public class HashCodeEqualsTest {
    public static void main(String[] args) {
        HashSet<Person> set = new HashSet<>();
        set.add(new Person("Alice"));

        // 동일한 이름의 객체를 검색하려고 함
        System.out.println(set.contains(new Person("Alice"))); // false
    }
}

 

더보기

원인

  • equals()는 동일한 객체라고 판단하지만, hashCode()를 재정의하지 않았으므로 다른 해시 버킷에 저장됩니다.
  • 결과적으로 HashSet은 데이터를 찾지 못합니다.

 

< 수정 >

import java.util.Objects;

class Person {
    String name;

    public Person(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Person person = (Person) obj;
        return name.equals(person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name); // name 기반으로 해시코드 생성
    }
}

public class HashCodeEqualsTest {
    public static void main(String[] args) {
        HashSet<Person> set = new HashSet<>();
        set.add(new Person("Alice"));

        // 동일한 이름의 객체를 검색
        System.out.println(set.contains(new Person("Alice"))); // true
    }
}

결과

  • equals()와 hashCode()를 함께 재정의하여, 동일한 이름의 객체를 올바르게 검색합니다.

 

왜 hashCode()를 함께 정의해야 하는가?

  1. 효율성:
    • hashCode()는 검색을 빠르게 하기 위해 사용됩니다.
      (equals()는 최종 확인 단계로만 호출되므로 성능 부담이 적음).
  2. 충돌 방지:
    • hashCode()가 올바르게 정의되지 않으면, 모든 데이터가 같은 해시 버킷에 저장되어 성능이 크게 저하됩니다.
      예: O(1) → O(n)
  3. 컬렉션 동작 보장:
    • HashMap, HashSet 등 해시 기반 컬렉션이 의도한 대로 작동하려면 반드시 두 메서드가 일관성 있어야 합니다.

정리

  • equals()는 객체의 논리적 동등성을 비교.
  • hashCode()는 빠른 검색과 데이터 저장을 위해 사용.
  • 두 메서드는 일관성을 유지해야 하며, 함께 재정의하지 않으면 해시 기반 자료구조가 잘못 동작하거나 성능이 저하될 수 있습니다.
    따라서 항상 equals()와 hashCode()를 함께 재정의하세요.

 

'Back-End (Web) > JAVA' 카테고리의 다른 글

[JAVA] JAVA 웹 기술의 역사  (0) 2024.12.12
[JAVA] 열거형 ( Enum )  (0) 2024.11.22
[JAVA] 자바의 정렬  (0) 2024.11.21
[JAVA] 응용 정리  (1) 2024.11.15
[JAVA] NULL  (0) 2024.11.13