이펙티브 자바 #item11 equals를 재정의하려거든 hashcode 또한 재정의하라

임현규·2023년 1월 10일
0

이펙티브 자바

목록 보기
11/47
post-thumbnail

Equals와 HashCode는 짝꿍

Equals를 구현했다면 HashCode 또한 반드시 구현해야 한다. 그 이유는 HashMap이나 HashSet과 같은 컬렌션의 원소로 사용하는 경우 Equals와 HashCode를 사용하는데 문제를 일으킬 수 있다.

HashMap의 getNode 메서드로 실제 key를 이용해 값을 가져오는 메서드이다. 이를 살펴보면 hash가 같은지 판단하고 key 입력이 null인지 그리고 equals를 이용해 동등성을 확인한다.

(자바의 HashMap은 내부 해시 충돌시 2차원 Node 테이블과 Sperate Chaining 방식으로 해결하고 있다.)

HashCode 일반 규약

  1. equals에 사용되는 정보가 변경되지 않으면 항상 같은 HashCode를 반환해야한다. (application을 다시 시작하는 경우 달라져도 된다.)
  2. equals가 두 객체를 같다고 판단했다면, 두 객체의 hashCode는 같아야 한다.
  3. equals가 두 객체를 다르다고 판단했더라도, 두 객체의1 hashCode가 서로 다른 값을 반환할 필요는 없다. 그러나 성능상을 위해서는 다른 값을 반환해야 해시테이블 성능이 좋아진다.

hashCode 구현

1. 소수를 활용한다.

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

소수로 31을 사용했는데 31은 최적화와 관련이있다. 31 * i는 (1 << 5) - i 와 같이 시프트 연산으로 컴파일러가 알아서 최적화해준다.

Short.hashCode는 단순하게 short value를 hashCode로 사용. 다만 Short 형은 크기가 작으므로 int로 형변환해준다.

Objects의 hashCode

Obejcts의 HashCode는 hashCode를 간결하게 구현할 수 있다. intellij 자동 완성에서는 이 메서드를 사용한다.

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

코드 자체는 간결하지만 책에 의하면 hashCode를 호출하는 비용이 꽤 크다고 한다. 매번 해쉬함수를 호출하고 싶지 않다면 캐싱하는 방법을 사용한다.

캐싱하는 방법은 몇가지 있지만 생성시 초기화하는 방법, 또는 lazy하게 초기화하는 방법이 있다. lazy 방식은 Thread-safe 하지 않으므로 이 부분을 고려해주어야 한다.

캐싱코드의 단점은 hashCode 변수가 인스턴스에 추가되고 이 부분을 구현해야 되서 꼭 필요한 객체의 인스턴스 변수를 남기고 싶은 때 좀 신경 쓰인다.

참고

https://d2.naver.com/helloworld/831311 (hashMap)

profile
엘 프사이 콩그루

0개의 댓글