[Java] equals 와 hashcode

Jongmyung Choi·2023년 10월 19일
2
post-custom-banner

개요

Item 11 . equals를 재정의하려거든 hashCode도 재정의하라

Effective Java에서 equals 를 재정의한 클래스라면, 반드시 hashCode 도 재정의해야 한다 라고 말하고 있다.
그 이유는 무엇인지에 대해 정리하고자 글을 작성하였다.

eqauls와 hashcode

equals()hashcode는 모두 Object 클래스에 정의되어있고 따라서 Java의 모든 객체는 equals와 hashcode를 상속 받는다.

equals()

public boolean equals(Object obj) {
    return (this == obj);
}

equals()는 두 객체의 동등성(Equality)을 비교하는 데 사용된다.
하지만 기본적으로 equals()는 객체의 메모리 주소를 비교하므로, 두 객체가 동일한 메모리 주소를 참조할 때(동일성을 비교) equals()는 true를 반환한다.

📢 동등성 : 객체의 내용이 같다면 같다.
동일성 : 객체의 주소값이 같다면 같다.

우리는 주소값이 아닌 객체의 내용을 기반으로 두 객체를 비교하는 경우가 많다.
VO(Value Object) 객체를 비교할 때 값만 비교하면 되므로 동등성 비교를 한다.
따라서 VO 비교를 위해서는 equals의 원래 정의(동일성 비교)를 동등성을 비교하도록 재정의 해줘야 한다.

❓ String 은 equals 메소드를 사용하면 값을 비교하던데?
-> String에서 equals를 오버라이드 하여 동등성을 비교하도록 재정의 했기 때문

hashcode()

public native int hashCode();

객체의 해시 코드(객체를 식별하는 하나의 고유 정수값)를 반환한다.

hashcode()는 주소값을 기반으로 생성하기 때문에 같은 값을 가진 객체여도 주소값이 다르다면 다른 해쉬코드를 반환한다.

예시코드

public class Man {
    private final String name;

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Man man = (Man) o;
        return Objects.equals(name, man.name);
    }
}

Man 클래스에서 equals()를 재정의하였기 때문에

public static void main(String[] args){
    Man man1 = new Man("hello");
    Man man2 = new Man("hello");
    
    System.out.println(man1.equals(man2));
}

의 값은 true가 된다.

그러면 이 상황에서는 어떨까?

public static void main(String[] args) {
    Set<Man> mans = new HashSet<>();
    mans.add(new Man("hello"));
    mans.add(new Man("hello"));

    System.out.println(mans.size());
}

이 코드에서 같은 객체(Man)을 넣어줬으므로 mans.size()가 1이 나올것으로 예상했지만 mans.size()는 2가 나오게 되었다.
이렇게 equals와 hashcode를 같이 재정의 해주지 않는다면 hash 값을 사용하는 Collection(HashSet, HashMap, HashTable)을 사용할 때 문제가 생긴다.

hashcode 재정의

hashcode 규약

  1. equals비교에 사용되는 정보가 변경되지 않았다면, 객체의 hashcode 메서드는 몇번을 호출해도 항상 일관된 값을 반환해야 한다.
    (단, Application을 다시 실행한다면 값이 달라져도 상관없다. (메모리 소가 달라지기 때문))
  2. equals메서드 통해 두 개의 객체가 같다고 판단했다면, 두 객체는 똑같은 hashcode 값을 반환해야 한다.
  3. equals메서드가 두 개의 객체를 다르다고 판단했다 하더라도, 두 객체의 hashcode가 서로 다른 값을 가질 필요는 없다. (Hash Collision)
    단, 다른 객체에 대해서는 다른 값을 반환해야 해시테이블의 성능이 좋아진다.

hashcode를 재정의 하지 않았을때 2번 규약에 문제가 생긴다.

재정의 코드

Hashcode를 재정의할때는 균등하게 분포되고 다른객체 일때는 다른 값을 반환하도록 해야하는데 이것은 수학적인 영역에 가까우므로 라이브러리나 IDE 기본 기능을 사용하도록 하자.

첫번째

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

intellij 의 Generate 기능을 사용하면 이렇게 자동으로 정의해주는데
Objects.hash 메서드는 hashCode 메서드를 재정의하기 위해 간편히 사용할 수 있는 메서드이지만 속도가 느리다.
-> 인자를 담기 위한 배열이 만들어지고 인자 중 기본 타입이 있다면 박싱과 언박싱도 거친다고 한다.

두번째
@EqualsAndHashCode 나 이것을 포함하는 @Data 어노테이션을 이용하면 자동으로 재정의를 해준다.

세번째
Apache Commons의 라이브러리를 사용하여 재정의하자

import org.apache.commons.lang3.builder.HashCodeBuilder;

@Override
public int hashCode() {
        final int PRIME = 31;
        return new HashCodeBuilder(getId()%2==0?getId()+1:getId(), PRIME).toHashCode();
    }

참고

https://tecoble.techcourse.co.kr/post/2020-07-29-equals-and-hashCode/

profile
총명한 개발자
post-custom-banner

0개의 댓글