# 아이템11 equals를 재정의 하려거든 hashCode도 재정의 하라

`equals를 재정의한 클래스 모두에서 hashCode도 재정의해야 한다.` 해당 클래스의 인스턴스를 `HashMap`이나 `HashSet` 같은 컬렉션의 원소로 사용할 때 문제를 일으킬 것이다.

### 다음은 Object 명세에서 발췌한 규약이다.

* `equals` 비교에 사용되는 정보가 변경되지 않았다면, 애플리케이션이 실행되는 동안 그 객체의 `hashCode` 메서드는 몇 번을 호출해도 일관되게 항상 같은 값을 반환해야 한다.
* `equals가 두 객체를 같다고 판단했다면, 두 객체의 hashCode는 똑같은 값을 반환해야 한다.`
* `equals가 두 객체를 다르다고 판단 했더라도 두 객체의 hashCode가 서로 다른 값을 반환할 필요는 없다.`

hashCode 재정의를 잘못했을 때 크게 문제가 되는 조항은 `두 번째다` 즉 논리적으로 같은 객체는 같은 해시코드를 반환해야 한다.

`HashMap`은 해시코드가 다른 엔트리끼리는 `동치성 비교`를 시도조차 하지 않도록 최적화되어 있으므로 별도의 객체를 HashMap의 Key로 사용할 경우 원하는 값을 리턴 받지 못할 것이다.

> 동등성이랑 동일성은 무엇일까? 동등성(동치성) : 두 객체가 동일한 정보를 담고 있는지? 동일성 : 두 객체가 물리적(주소)으로 같은지?
>
> ```java
>     @Override public int hashCode() { return 42; }
> ```
>
> 이 코드는 동치인 모든 객체에서 똑같은 `해시코드`를 반환하니 적법하다. 하지만 끔찍하게도 모든 객체에게 똑같은 값만 내어주므로 모든 객체가 해시테이블의 버킷 하나에 담겨 마치 연결 리스트처럼 동작한다.

`좋은 해시 함수라면 서로 다른 인스턴스에 다른 해시코드를 반환한다.`

### 다음은 hashCode를 작성하는 간단한 요령이다.

* 객체의 나머지 `핵심 필드` 각각에 대해 다음 작업을 수행한다.
* 해당 필드의 해시코드 c를 계산한다.
  * `기본 타입 필드라면 Type.hashCode(f)를 수행한다.`
  * `참조 타입 필드일 경우 표준형을 만들어 hashCode를 호출한다.` 필드의 값이 null이면 0을 사용한다.
  * 필드가 배열이라면 핵심 원소 각각을 별도 필드처럼 다룬다. 모든 원소가 핵심 원소라면 Arrays.hashCode를 사용한다.
* 이전 단계에서 계산한 해시코드 c로 result를 갱신한다.
  * result = 31 \* result + c
* result를 반환한다.

  ```java
    @Override public int hashCode() {
        int result = Short.hashCode(areaCode);
        result = 31 * Short.hashCode(prefix);
        result = 31 * Short.hashCode(linenum);
        return result;
    }
  ```

### 그렇다면 왜 31을 사용했을까?

31이 `홀수` 이면서 `소수(prime)`이기 때문이다. 만약 이 숫자가 짝수이고 오버플로우 가 발생한다면 정보를 잃게 된다.

위와 같은 계산식이 번거로울 수 있다. 이를 간편하게 제공하기 위한 방법으로 `Objects` 클래스의 정적 메서드를 제공한다.

Objects 클래스는 임의의 개수만큼 객체를 받아 해시코드를 계산해주는 정적 메서드인 hash를 제공한다.

```java
    @Overrider public int hashCode() {
        return Objects.hash(linenum, prefex, areaCode);
    }
```

이 메서드를 활용하면 앞서의 요령대로 구현한 코드와 비슷한 수준의 hashCode 함수를 단 한줄로 작성할 수 있다. `하지만 아쉽게도 속도는 더 느리다.` 입력 인수를 담기 위한 배열이 만들어지고, 입력 중 기본 타입이 있다면 `박싱`과 `언박싱`도 거쳐야 하기 때문이다. `그러니 hash 메서드는 성능에 민감하지 않은 상황에서만 사용하자`

클래스가 불변이고 해시코드를 계산하는 비용이 크다면, 매번 새로 계산하기 보다는 `캐싱`하는 방식을 고려해야 한다.

`hashCode가 반환하는 값의 생성 규칙을 API 사용자에게 자세히 공표하지 말자.` 그래야 클라이언트가 이 값에 의지하지 않게 되고, 추후에 계산 방식을 바꿀 수도 있다.

## 정리

`equals를 재정의할 때는 hashCode도 반드시 재정의해야 한다.` 그렇지 않으면 프로그램이 제대로 동작하지 않을 것이다. 재정의한 hashCode는 Object의 API 문서에 기술된 일반 규약을 따라야 하며, \`서로 다른 인스턴스라면 되도록 해시코드도 서로 다르게 구현해야 한다.`렇게 구현하기가 어렵지는 않지만 조금 따분한 일이긴 하다. 하지만 걱정마시라`toValue\`\`레임워크를 사용하면 멋진 equals와 hashCode를 자동으로 만들어준다.
