2017년 5월 6일 토요일

effective java 규칙 9. equals를 재정의할 때는 반드시 hashCode 도 재정의하라.

Object 의 hashcode 

Object 의 hash code 는 heap 에 생성되는 객체의 주소(reference 주소)를 바탕으로 생성된다.
따라서 객체의 주소가 다르다면 (값은 같을 지라도) 같은 hash code 는 가질 수 없다.

hashcode 의 목적

객체를 구별하는데 그 목적이 있다.
구별이란 주소(reference)의 동치 판단의 의미와 의 동치 판단으로 구분된다.

객체를 구별하는데 그 객체가 저장된 주소 가 동일한 즉 실제로 똑같은 같은 메모리상 위치에 저장된 같은 객체인지를 판단하려면
Object 의 기본적인 hash code 를 사용하면된다.

그러나 객체의 중요필드의 값이 같다면 같은 객체라고 판단하는 비즈니스 로직이 있을 경우
hashcode 는 equals 메소드와 마찬가지로 override 되어야 한다.
그래야만 hashcode 메소드의 일반규약을 사용하는 클래스에서 제대로된 작동을 보장할 수 있다. (HashMap 이나 HashTable 등..)

hashCode() 의 일반규약

두 객체의 equals 의 결과가 true 이면 hashcode 는 동일
두 객체의 equals 의 결과가 false 이더라도 hashcode 는 항상 다르지는 않다. = 객체가 같지 않더라도 hashcode 는 동일할 수 있다
    - hashcode 란 계산된 결과(숫자) 이기 때문에 계산 알고리즘 (Hashing Algorith) 이 항상 완전한 해시 함수를 구현하지 않으면 
    hashcode 는 동일 할 수 있다.
    - Boolean 같이 서로 구별되는 객체의 종류가 적거나 Number 형 객체는 객체가 나타내려는 값 자체를 해시 값으로 사용할 수 있기 때문에 완전한 해시 함수 대상이 되지만 String 이나 pojo 에 대하여 완전한 해시 함수를 제작하는 것은 사실상 불가능
    - 그러나 거의 대부분 서로 다른 hashCode 값이 나오면 hashcode 를 사용하는 collections 에서 성능이 향상된다.

※ 완전한 해시 함수
x.equals(y) 가 false 일때 x.hashCode() != y.hashCode() 가 항상 성립할때

hashCode() override 시 가장 중요한 핵심 규약은 hashCode() 일반 규약의 첫번째 설명 이다.
-같은 객체는 같은 해시 코드값을 가진다.-

hashCode 지침
다음은 effective java 에서 제안하는 hashCode 지침이다.
   1. 초기값으로 0이 아닌 int 상수를 지정
  1. 중요 필드값을 number 형태의 결과로 나타날수 있도록 계산
    • boolean 일경우 f ? 1: 0
    • int 보다 범위가 작은 타입(byte,char,short,int)일 경우 (int) f 
    • long 일 경우 (int) (f ^ (f >>> 32))
    • float : Float.floatToIntBits(f) , double : Double.doubleToLongBits(f)
    • 객체참조 일때 hashCode 메서드를 재귀적으로 호출하여 계산, null 일 경우 0 반환
    • 배열일때 각 원소별 계산 후 result = 31 * result + c 로 결합 - c는 계산값 result 는 초기값(누산값)
      • Arrays.hashCode 메서드 가운데 하나를 사용 가능
    • 결합 : result = 31 * result + c 로 결합 - c는 계산값 result 는 초기값(누산값)
      • 31 의미 : 소수이면서 홀수 , 분포도를 좋게 하기 위해 소수를 사용, Mersenne prime
      • * 곱셈의 의미 : (String 해시함수가 곱셈이 없이 단순 누산이였다면 순서만 바뀐 문자열의 해시 코드가 동일해짐)
  2. result 반환
  3. 점검 - 동치 관계일때 같은 해시 코드 값인지 확인 및 테스트
  4. 중보필드는 계산과정에서 제외, 다른 필드에서 유도될 수 있는 필드 제외

그외 검토할 사항

변경불가능 객체의 hashCode 계산시 계산비용이 높다면 캐시에 저장할 필요성
    대부분의 객체가 해시 키로 사용된다면 객체를 생성할 때 해시 코드를 계산
    그렇지 않다면 hashCode 값의 초기화 지연 기법

성능 개선목적으로 중요 필드의 계산 과정을 누락시키면 안됨 
    - 해시값 품질이 저하되어 해시 테이블의 성능을 매우 떨어뜨림

샘플코드


private int a;
private int b;
private int c;

public HashCodeTest(int a, int b, int c) {
this.a = a;
this.b = b;
this.c = c;
}

// 해시코드 초기화 지연 기법 사용시
private volatile int hashCode;
@Override
public int hashCode() {

int result = hashCode;
// 초기화 지연 기법 적용
if(result == 0) {
result = 17;
result = 31 * result + a;
result = 31 * result + b;
result = 31 * result + c;

hashCode = result;
}

return result;

}

댓글 없음:

댓글 쓰기

Intelij 설정 및 plugin

1. preferences... (settings...) Appearance & Behavior > Appearance - Window Options        ✓   Show memory indicator Editor &...