티스토리 뷰

해시 자료 구조에서 equals 메서드와 hashCode 메서드를 재정의 해야하는 이유?

 해시 자료 구조를 사용할려면 equals 메서드와 hashCode 메서드를 구현해야한다. 왜 두 메서드를 재정의 해야하는 이유는 아래 예제 코드를 통해 확인해본다.

Object의 기본 기능

 우선 equals 메서드와 hashCode 메서드는 Object 클래스에 기본 기능이다. 클래스를 만들 때 두 메서드를 재정의하지 않으면, 해시 자료 구조에서 Object가 기본으로 제공하는 hashCode, equals 메서드(참조값을 기반으로 동작하는)를 사용하게 된다.

  • hashCode(): 객체의 참조값을 기반으로 해시 코드를 반환한다.
  • equals(): 동일성 비교를 한다. 객체의 참조값이 같아야 true를 반환한다.

예제1 hashCode, equals를 모두 구현하지 않은 경우

public class Member {
    
    private String id;

    public String getId() {
        return id;
    }

    @Override
    public String toString() {
        return "Member{" +
                "id='" + id + '\'' +
                '}';
    }
}
public class ExecuteHashCode {

    public static void main(String[] args) {

        Set<Member> set = new HashSet<>();

        Member member1 = new Member("A");
        Member member2 = new Member("A");

        System.out.println("member1 hashCode() = " + member1.hashCode());
        System.out.println("member2 hashCode() = " + member2.hashCode());

        set.add(member1);
        set.add(member2);

        System.out.println(set);

        // 검색용 Member 생성
        Member memberSearch = new Member("A");
        System.out.println("memberSearch hashCode() = " + memberSearch.hashCode());
        boolean contains = set.contains(memberSearch);
        System.out.println("contains = " + contains);

    }
}
member1 hashCode() = 1239731077
member2 hashCode() = 357863579
[Member{id='A'}, Member{id='A'}]
memberSearch hashCode() = 1945604815
contains = false

 위의 예제에서 member1과 member2가 동일한 id를 가지고 있더라도(논리적으로 같은 회원) HashSet에 두 회원이 저장된 것을 확인할 수 있다. (※ Set 자료구조는 중복 값 불가) 그리고 검색용으로 동일한 id를 가진 Member 객체를 생성 후 포함 여부를 확인 결과 false가 나온 걸 확인할 수 있다. 그 이유는 member1, member2, memberSearch 의 hashCode, equals(Object의 기본 제공 메서드) 메서드는 참조값을 기반으로 해서 해시코드를 생성한다.따라서 논리적으로는 같아도 해시 코드가 다르니 HashSet에서는 다른 값으로 본다. 아래 그림은 위의 예제를 간단히 그림으로 표현했다.

예제1의 구조를 간단히 그림으로 표현

예제2 hashCode는 구현 equals를 구현하지 않은 경우

public class Member {

    private String id;

    public Member(String id) {
        this.id = id;
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }

    public String getId() {
        return id;
    }

    @Override
    public String toString() {
        return "Member{" +
                "id='" + id + '\'' +
                '}';
    }
}
member1 hashCode() = 96
member2 hashCode() = 96
[Member{id='A'}, Member{id='A'}]
memberSearch hashCode() = 96
contains = false

 위의 예제2에서 출력 값(실행 부분은 수정한게 없으므로 생략)은 예제1과 다를게 없어 보이지만 hashCode의 값이 같다는게 보인다. 오버라이드된 hashCode 메서드를 보면 id를 기준으로 해시 코드를 생성했다. 그리고 해시 코드가 같으므로 해시 인덱스 생성 후 같은 해시 인덱스에 저장된다. 하지만 equals(참조값 기반)를 재정의 하지 않았으므로 중복 저장이 되었다. 그리고 검색은 equals로 비교하지만 마찬가지로 재정의 하지 않았으므로 검색이 불가능한다. 따라서 hashCode만 재정의 했더라도 중복 저장과 검색 불가는 해결하지 못했다. 아래 그림은 예제2를 간단히 표현했다.

  예제3 hashCode와 equals를 구현한 경우

public class Member {

    private String id;

    public Member(String id) {
        this.id = id;
    }

    @Override
    public int hashCode() {
        return Objects.hash(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);
    }

    public String getId() {
        return id;
    }

    @Override
    public String toString() {
        return "Member{" +
                "id='" + id + '\'' +
                '}';
    }
}
member1 hashCode() = 96
member2 hashCode() = 96
[Member{id='A'}]
memberSearch hashCode() = 96
contains = true

 위의 예제3에서 마지막으로 equals를 재정의 후 논리적으로 동일한 Member의 중복 저장 불가와 논리적으로 같은 Member를 생성 후 검색이 가능한 것을 확인했다. 아래 그림은 예제3을 간단히 표현했다.

예제3의 구조를 간단히 그림으로 표현

해시 충돌

 위의 예제를 보면 궁금한게 생길 것이다. hashCode를 생성할 때 같은 hashCode가 생성되면 어떻게 될까? 예제 2와 같이 같은 인덱스에 저장이 된다. 이와 같은 문제를 해시 충돌이라고 한다. 그러나 해시 코드가 충돌해도 equals를 통해 비교하기 때문에 저장과 검색 등 문제가 되지 않는다. 밑에 equals 메서드는 참조 값을 비교하고, 객체 정보를 비교 후 객체 내의 속성 값을 비교한다. (equals는 IDE로 재정의 함)

    @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 메서드를 재정의 할 필요가 있다. hashCode와 equals를 둘다 재정의 하지 않을 경우 논리적으로 같은 객체라도 참조값을 비교 후 저장하기 때문에 중복저장과 검색이 불가능하고, hashCode만 재정의할 경우 해시 코드가 같지만 equals메서드 때문에 논리적으로 같더라도 중복으로 저장하고 검색이 불가능한다. 하지만 둘다 재정의 할 경우 저장과 검색을 하기 전에 equals를 통해 비교 후 저장과 검색을 하기 때문에 논리적으로 같은 객체의 중복 저장과 검색을 해결할 수 있다.

 


본 포스팅은 “김영한의 실전 자바 - 중급 1편/인프런”를 학습한 내용을 정리한 것

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

<Java> volatile이란?  (0) 2024.10.18
<Java> Thread join() 메서드란?  (3) 2024.10.16
<Java> 불변 객체가 필요한 이유?  (2) 2024.10.13
<Java> 열거형(ENUM)이 생긴 이유?  (0) 2024.06.20
<Java> OCP(Open-Closed Principle) 원칙이란?  (0) 2024.05.22
댓글
최근에 올라온 글
«   2026/03   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함
Total
Today
Yesterday