[Effective Java] 3장 객체 생성과 파괴 - 아이템 11. equals를 재정의하려거든 hashCode도 재정의하라

배상규·2023년 10월 12일
0

이펙티브 자바

목록 보기
10/12
post-thumbnail

들어 가면서

equals를 재정의한 클래스는 hashCode도 재정의 해야한다.
그렇지 않는다면 hashCode 일반 규약을 어기게 되어 HashMap이나 HashSet 같은 컬렉션의 원소로 사용할 때 문제를 일으키기 때문이다.


HashCode 규약

  • equals비교에 사용되는 정보가 변경되지 않다면, 객체의 hashCode 메서드는 항상 같은 값을 반환한다.
  • equals가 두 객체를 같다고 판단하면, 두 객체는 같은 hashCode를 반환한다.
  • equals가 두 객체를 다르다고 판단하더라고 다른 hashCode를 반환할 필요는 없으나, 다른 값을 반환해야 해시 테이블의 성능이 좋다.

논리적으로 같은 객체는 같은 해시코드를 반환한다.

Map<PhoneNumber, String> map = new HashMap<>();
map.put(new PhoneNumber(010,1111,1111), new Person("테스트"));

이 코드에 map.get(new PhoneNumber(010,1111,1111))을 실행하면 테스트값이 아닌 null 값을 반환한다.
이유는 PhoneNumber클래스에 hashCode를 재정의 하지 않았기 때문에, 다른 해시코드를 반환하기 때문이다.
hashMap은 해시코드가 다른 엔트리 끼리는 비교를 시도조차 하지 않도록 최적화 되어 있다.


좋은 해시코드를 작성하는 법은?

좋은 해시코드는 서로 다른 인스턴스 마다 다른 해시코드를 반환하는 것이 이상적인 해시 함수 이며, 32비트 정수 범위에 균일하게 분배되어야 한다.

1.int 변수인 result를 선언한 후 값을 c로 초기화한다.

  • 이 때, c는 해당 객체의 첫번째 핵심 필드를 단계 2.1 방식으로 계산한 해시코드이다.
  • 여기서 핵심 필드는 equals 비교에 사용되는 필드를 말한다.
  1. 해당 객체의 나머지 핵심 필드인 f 각각에 대해 다음 작업을 수행한다.
  • 해당 필드의 해시코드 c 를 계산한다.
    • 기본 타입 필드라면,Type.hashCode(f)를 수행한다
    • 참조 타입 필드면서, 이 클래스의 equals 메서드가 이 필드의 equals를 재귀적 호출하여 비교하면, 이 필드의 hashCode를 재귀적으로 호출한다.
    • 필드가 배열이라면, 핵심 원소 각각 별도 필드처럼 다룬다. 모든 원소가 핵심 원소라면 Arrays.hashCode를 사용한다.

주의할점

  • equals 비교에 사용되지 않은 필드는 반드시 제외한다.
  • 성능 향상을 위해 해시코드를 계산할 때 핵심 필드를 생랴하면 안 된다.
  • 참조 타입 필드가 null일 경우 0을 사용한다.
  • 31을 곱하는 이유는 홀수이면서 소수 이기 때문이다.

전형적인 hashCode 메서드

@Override
    public int hashCode() {
        int result = Integer.hashCode(areaCode);
        result = 31 * result + Integer.hashCode(prefix);
        result = 31 * result + Integer.hashCode(lineNum);
        return result;
    }

PhoneNumber 핵심 필드 3개를 이용해 간단한 계산만 수행하고, 이 과정에 비결정적 요소는 전혀 없다.
-> 동치 이기 때문에 PhoneNumber 인스턴스는 서로 같은 해시코드를 가질 것이 확실하다.


핵심

equals를 재정의할 때는 hashCode를 반드시 재정의해야 하며, 그렇지 않으면 프로그램이 제대로 동작하지 않을것이며, 서로 다른 인스턴스라면 되도록 해시코드가 서로 다르게 구현해야한다.

profile
기록에 성장을

0개의 댓글