본문 바로가기

Dev Book Review/Effective Java

[Effective Java] item11. equals를 재정의하려거든 hashCode도 재정의하라

equals를 재정의한 클래스 모두에서 hasCode도 재정의해야한다.
일반 규약을 어기게 되어 HashMap이나 HashSet 같은 컬렉션 원소로 사용할 때 문제를 일으킬 수 있다.

 

1. Object 명세의 3가지 규약

  • equals() 에 사용되는 정보가 변경되지 않았다 -> 항상 같은 값이어야한다.
  • equals(object) true이다 -> 두 객체의 hashCode는 같다.
  • equals(object) false이다 -> hasCode가 다를 필요는 없다.

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

 

2. hashcode 동작 방법

좋은 해시 함수라면 서로 다른 인스턴스에 다른 해시코드를 반환한다.
주어진 인스턴스들을 균일하게 분배한다.

만약 같은 값을 반환한다면, 객체가 해시테이블 버킷하나에 담기고, 그 객체들이 연결리스트처럼 동작한다.

@Override public int hashCode(){return 42;}

수행시간이 O(1)인 해시 테이블이 O(n)으로 느려진다.

 

3. 좋은 HashCode를 작성하는 방법

ㄱ. 핵심 필드를 ㄴ.a 방법으로 계산한다

int result; 를 선언한 후 값 c로 초기화하는데, 이때 c는 핵심필드를 계산한 값이다.

ㄴ. 객체의 나머지 핵심 필드에 대해 계산을 수행한다.

a. 해당 필드의 해시 코드를 계산한다.

  • 기본 타입 필드 : Type.hashCode(f) = Integer.hashCode(f);
  • 참조 타입 필드 + equals가 재귀적으로 호출 : hascode도 재귀적 호출
    복잡할 것 같으면 표준형(canoical representation)을 만들어 그 표준형의 hashCode를 호출한다.
  • 필드가 배열이면, 핵심원소 각가을 별도 필드처럼 다룬다. (Arrays.hashCode)

b. ㄴ.a에서 계산한 해시코드로 갱신한다 : result = 31 * result + c

ㄷ. result를 반환한다.

 

4. HashCode 작성시 주의 점

ㄱ. 동치인 인스턴스에 대해 똑같은 해시코드를 반환할지 자문하자

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

ㄴ. equals 비교에 사용되지 않는 필드는 계산에서 무시하자.

ㄷ. 성능을 높이기 위해 해시코드 계산시 핵심 필드를 생략해서는 안된다.

속도는 빨라지지만 해시 품질이 나빠져, 해시테이블 성능을 떨어뜨릴 수 있다.

ㄹ. hashCode가 반환하는 값의 생성규칙을 API 사용자에게 자세히 공표하지 말자

클라이언트 의존도가 낮아지고, 추후에 계산방식을 바꿀 수 있기 때문이다.

현재 String, Integer 등 여러 자바 라이브러리에서 hashCode 메서드가 반환하는 정확한 값을 알려주고있다.

 

5. Object 클래스의 hash() 메서드

장점 : hashCode 함수를 단 한줄로 작성할 수 있다.

단점 : 속도가 더느리다

  • 입력 인수를 담기위한 배열 생성
  • 기본타입이 있을 때 박싱과 언박싱

객체의 나머지 핵심 필드에 대해 수행한다.

@Override public int hashCode(){
	return Objects.hash(lineNum, prefix, areaCode);
}

 

6. 캐싱 전략

클래스가 불변 + 해시코드 계산 비용이 다 -> 새로 계산X

객체가 주로 해시의 키로 사용될 것 같을때 = 인스턴스 생성시 해시코드 계산
객체가 주로 해시의 키로 사용되지 않을 것 같을 때 = 지연 초기화 전략 (hashCode가 처음 불릴 때 계산)

private int hashCode;

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