[Effective Java] item 11 : equals를 재정의하려거든 hashCode도 재정의하라

DEINGVELOP·2022년 11월 2일
0

Effective Java

목록 보기
4/19

앞의 아이템에서 살펴본 equals를 재정의할 때에는, hashCode도 반드시 같이 만들어줘야 한다. 심지어 Lombock에서는 @EqualsAndHashCode로 어노테이션이 합쳐져있어, 항상 함께 만들어줘야 하기도 한다. (잘 쓰이지는 않지만 Google의 AutoValue라는 라이브러리 안에서도 함께 만들어지게 되어있다.)

hashCode 규약

  • equals 비교에 사용되는 정보가 변경되지 않았다면, hashCode는 매번 같은 값을 리턴해야 한다.
  • 두 객체에 대한 equals가 같다면, hashCode의 값도 같아야 한다.
  • 두 객체에 대한 equals가 다르더라도, hashCode의 값은 같을 수 있지만 해시 테이블 성능을 고려해 다른 값을 리턴하는 것이 좋다.

    해시테이블의 성능 고려?

    제대로 사용하지 않으면 LinkedList의 원리와 다를 바 없게 동작하기 때문에, Hash를 제대로 효율적으로 쓰기 위해서는 HashCode를 올바르게 쓰는 방법을 알아야 하는 것임


HashCode 구현 방법

@Override public int hashCode() {
        int result = Short.hashCode(areaCode);
        result = 31 * result + Short.hashCode(prefix);
        result = 31 * result + Short.hashCode(lineNum);
        return result;
    }
  1. 핵심 필드 하나의 값의 해쉬값을 계산해서 result값을 초기화한다.

  2. 해당 객체의 나머지 핵심 필드 각각에 대해 다음 작업을 수행한다.

    • 기본 타입은 Type.hashCode
    • 참조 타입은 해당 필드의 hashCode
    • 배열은 모든 원소를 재귀적으로 위의 로직을 적용하거나, Arrays.hashCode
  3. result = 31 * result + 해당 필드의 hashCode 계산값

    31을 쓰는 이유?

    31이라는 소수를 썼을 때, hash collision이 가장 적게 났다고 한다. 그래서 이제는 관습처럼 쓴다고.

  4. result를 리턴한다.


hashCode를 직접 구현하지 않으려면?

  • Objects 클래스의 hash 메소드 사용
    : 보통 우리가 인텔리제이에서 사용하는 방법

    @Override
    public int hashCode() {
    	return Objects.hash(areaCode, prefix, lineNum);
    }
    • 안에 타고 들어가보면 위에서 구현한 것과 유사하게 구현되어있다.

    • Object.hash()를 사용하면 된다.

    • Lombock으로 사용하면 -> 내가 테스트 할 필요가 없다. -> 가장 추천됨

  • Google guava 라이브러리 중 hash.Hashing을 사용

  • 캐싱을 사용해 불변 클래스의 해시 코드 계산 비용을 줄일 수 있다.


주의할 것

  • 지연 초기화 기법을 사용할 때, 스레드 안정성을 신경써야 한다. (아이템 83)
  • 성능 때문에 핵심 필드를 해시 코드 계산할 때 빼면 안 된다.
  • 해시코드 계산 규칙을 API에 노출하지 않는 것이 좋다.

0개의 댓글