hashCode의 역할
hashCode는 객체의 해시코드를 반환해주는 책임을 수행하는 메소드이다. 해시라는 값은 같은 객체라면 같은 해시값을, 아니면 다른 해시값값을 반환해야야한다.
hashCode는 Set,HashMap 등의 Collections에서 해시를 직접 사용하는 객체에 영향을 줄 수 있다.
hashCode 규약
hashCode도 equals와 마찬가지지로 규약이 있다. JavaDocs로 작성된 Object명세에 다음과 같이 작성되어 있다.
- equals 를 통한 비교 정보가 달라지지 않았으면 hashCode도 변하지 않아야 한다. 단 어플리케이션이 재실행되면 값이 변할 수 있다.
- 두 객체의 equals의 결과가 true이면 같은 hashCode 를 가진다
- 두 객체의 equals의 결과가 false여도 서로 다른 hashCode를 표현할 필요는 없다. 단 다른값으로 표현해야만 hashtable의 성능이 좋아진다.
equals는 logical equality을 표현한다
item 10의 내용에서 equals 메소드를 오버라이딩하여 logical equality 를 표현할 때 사용한다. 즉 equals 메소드를 통해 물리적으론 다른 위치에 있는 두 객체라도 논리적으로 같은 객체로 파악할 수 있다.
즉 equals 메소드의 결과가 true인 두 객체는 논리적으로 같은 객체임을 의미한다.
equals만 정의하고 hashCode를 정의하지 않는 경우
원래 hashCode를 해부하여 보고 싶었으나 JNI로 구현되어 있어 보지 못했다.
equals 를 통해 logical equality를 표현하여도 hashCode가 다르다면 HashMap, HashSet같은 hashCode에 의존되는 객체를 사용할 때 논리적으로 같은 객체임에도 다른 객체로 판별되어 객체가 저장되어 있음에도 찾지 못하는 문제가 발생할 수 있다.
hashCode작성법
hashCode는 다음의 방법을 유의해서 작성해야 한다.
- equals 메소드에 사용되는 모든 필드들을 이용하여 hash를 계산한다.
- hash결과는 int 범위 내어야 한다.
- 서로 다른 두 객체는 서로 다른 hash를 가져야 한다.
- hashCode의 생성 규칙은 자세히 설명하지 말자
- hashCode에 버그가 발견되었거나 더 성능이 좋은 방식이 있음에도 불구하고 이미 공표가 되어버린 hashCode 생성 로직을 변경할 시 이를 의존하는 다른 기능들에 문제가 발생하기에 바꾸기가 어려워지는 문제를 방지하자는 의미이다.
1. 소수 X result
hash결과를 누적하는 result에 대하여 아래의 식으로 result를 누적한다.
result=필드의 해시값 X result X 소수
소수는 31를 이용하는 경우가 많다. String 객체에서는 hashCode를 다음과 같은 결과롤 반환한다고 JavaDocs에 작성되어 있다.
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
각 원소에 대해 31를 곱하고 더하는 방식으로 이용한다.
왜 많은 소수중에 31일까에 의문에 대해서 effective java 는 다음과 같이 설명한다.
- 31은 홀수이며 소수이다.(메르센 소수)
- 시프트 연산과 뺏셈을 하나의 연산으로 대체할 수 있다.
- 31* i == (i << 5) - i
2번의 경우는 VM이 성능이 좋아 최적화를 잘 해준다고 설명한다.
물론 모든 hash 계산에서 31이 적합한 소수는 아니다. 스택 오버플로우 커뮤니티에도 이에 대한 논쟁이 있다. What is a sensible prime for hashcode calculation?
2. Objects.hash
Objects.hash()
: 매개변수로 받은 객체들을 통해 hash를 계산해주는 메소드.
intelij 의 Generate기능인 equals and hashcode
를 이용할 시 이 메소드를 이용하게 된다.
public class Point {
private final int y;
private final int x;
// 생성자, equals 생략
@Override
public int hashCode() {
return Objects.hash(y, x);
}
}
이 메소드를 사용하면 hash계산을 쉽게 할 수 있느나 파라미터 이동간 boxing, unboxing이 발생하기에 성능 저하의 문제가 발생할 수 있다.
결론
equals는 두 객체가 같다는 것을 의미하므로 같은 객체면 같은 값을 반환해야 하는 hashCode도 같은 결과를 내도록 재작성해야한다는 의미이다.
Reference
'study > effective java' 카테고리의 다른 글
[item13] clone 오버라이딩은 주의해서 진행해라 (0) | 2023.05.02 |
---|---|
[item12] toString은 항상 오버라이딩하라 (0) | 2023.05.02 |
[item10] equals는 일반 규약을 지켜 재정의해라 (0) | 2023.05.01 |
[item9] try-finally보다 try-with-resources를 사용해라 (0) | 2023.04.25 |
[item7] 다 쓴 객체 참조를 해제하라 (0) | 2023.04.25 |