■ Object 명세 규약
- equals 비교에 사용되는 정보가 변경되지 않았다면, 애플리케이션이 실행되는 동안 그 객체의 hashCode 메서드는 몇번을 호출해도 일관되게 항상 같은 값을 반환해야 한다.
- equals(Object)가 두 객체를 같다고 판단했으면, 두 객체의 hashCode는 똑같은 값을 반환해야한다.
- equals(Object)가 두 객체를 다르다고 판단했더라도, 두 객체의 hashCode가 서로 다른 값을 반환할 필요는 없다. 단, 다른 객체에 대해서는 다른 값을 반환해야 해시 테이블의 성능이 좋아진다.
hashCode 재정의를 하지 않았을 경우 문제가 되는 조합은 두번째 조항이다. 즉, 논리적으로 같은 객체는 같은 해시 코드를 반환해야한다.
아래와 같이 PhoneNumber 클래스가 있을 때 hashCode를 재정의 하지 않으면 hashMap 에서 해당 객체를 다시 조회하려고할 때 null이 반환되는 것을 볼 수 있다.
import java.util.HashMap;
import java.util.Map;
public class PhoneNumer {
int n1;
int n2;
int n3;
public PhoneNumer(int n1, int n2, int n3){
this.n1 = n1;
this.n2 = n2;
this.n3 = n3;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof PhoneNumer)) return false;
PhoneNumer that = (PhoneNumer) o;
return n1 == that.n1 &&
n2 == that.n2 &&
n3 == that.n3;
}
public static void main(String[] args) {
Map<PhoneNumer, String> m = new HashMap<>();
m.put(new PhoneNumer(707,867,5309), "제니");
System.out.println(m.get(new PhoneNumer(707,867,5309))); //hashCode를 재정의 하지 않으면 null 반환
}
}
■ Object hash 관련 메서드
Objects.hash()
- 임의의 길이를 가진 데이터를 입력받아 일정한 길이의 비트열로 반환시켜주는 함수. 즉, 키를 Hash로 바꾸어주는 역할을 한다.
- 다양한 길이를 일정한 길이인 해시로 변경하여 저장소를 효율적으로 운영할 수 있도록 도와준다.
- Hash()에 의해 반환된 데이터 고유의 숫자 값을 hashCode라고 한다.
Objects.hashCode()
- 객체의 hashCode값을 반환 시켜주는 함수 이다.
- hashCode는 Heap에 있는 객체의 메모리 주소를 바탕으로 생성된다.
- 반환된 hashCode는 Object를 식별할 수 있는 하나의 Integer값을 의미한다.
PhoneNumber 클래스의 hashCode를 다시 재정의해보겠다. n1, n2, n3을 이용해서 Objects.hash 메서드를 호출하여 Hash로 바꾸어주는 로직을 호출한다.
package ch3.hoon.item11;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class PhoneNumer {
@Override
public int hashCode() {
return Objects.hash(n1, n2, n3);
}
}
Objects.hash 메서드 내부를 보면 입력받은 파라미터를 Arrays.hashCode 메서드를 이용하여 하나의 hashCode로 바꿔준다.
public static int hash(Object... values) {
return Arrays.hashCode(values);
}
Arrays.hashCode 구현은 아래 코드를 참조하면된다.
public static int hashCode(Object[] a) {
if (a == null) {
return 0;
} else {
int result = 1;
Object[] var2 = a;
int var3 = a.length;
for(int var4 = 0; var4 < var3; ++var4) {
Object element = var2[var4];
result = 31 * result + (element == null ? 0 : element.hashCode());
}
return result;
}
}
■ hashCode() 구현
- 좋은 hashCode 메서드는 서로 다른 인스턴스에 대해서 다른 해시 코드를 반환하도록 작성해야한다.
■ hashCode() 편하게 구현하는 방법
- IDE에서 제공하는 equals and hashCode 만드는 방법 사용
- @EqualsAndHashCode (Lombok)
연관관계가 복잡해질경우 @EqualsAndHashCode에서 서로 참조하는 경우 무한 루프가 발생할 수 있으므로 of 속성을 이용하여 보통 id 값만을 사용. callSuper 속성의 경우는 부모 클래스의 필드까지 감안할지 안할지에 대해서 설정할 수 있다.
@EqualsAndHashCode(of = "userId", callSuper = false)
- @AutoValue (Google)
■ 재 정의 시 주의할 점
- 불변 객체에 대해서 hashCode 생성 비용이 많이 든다면 캐싱을 고려하기
- 성능을 높이기 위해서 핵심필드를 제외하고 hashCode를 계산하지 않기
- hashCode가 반환하는 값의 생성 규칙을 API 사용자에게 자세히 공표하지 않기
- 클라이언트가 hashCode 값에 의지해서 코드를 짜지 않기 위함
REFERENCE
'Effective Java' 카테고리의 다른 글
[Effective Java] 아이템13 clone 재정의는 주의해서 진행하라 (0) | 2021.02.01 |
---|---|
[Effective Java] 아이템12 toString을 항상 재정의하라 (0) | 2021.01.24 |
[Effective Java] 아이템9 try-finally 보다는 try-with-resources를 사용하라 (0) | 2021.01.24 |
[Effective Java] 아이템10 equals는 일반 규약을 지켜 재정의하라 (0) | 2021.01.22 |
[Effective Java] 아이템8 finalizer와 cleaner 사용을 피하라 (0) | 2021.01.21 |