👉 equals를 재정의한 클래스 모두에서 hashcode도 재정의해야 한다. 그렇지 않으면 hashcode 일반 규약을 어기게 되어 해당 클래스의 인스턴스를 HashMap이나 HashSet 같은 컬렉션의 원소로 사용할 때 문제를 일으킨다.
👉 규약
equals 비교에 사용되는 정보가 변경되지 않았다면 애플리케이션이 실행되는 동안 hashCode 메서드를 몇 번 호출하더라도 같은 값을 반환해야 한다.
euquals가 두 객체를 같다고 판단했다면, 두 객체의 hashcode는 똑같은 값을 반환해야 한다.
equals가 동일하지 않다고 판단하더라도 hashCode가 서로 다른 값을 반환할 필요는 없다. 하지만, 다른 값을 반환해야 해시테이블의 성능이 좋아진다.
즉, 논리적으로 같은 객체는 같은 해시코드를 반환해야 한다.
👉 hashcode를 재정의 하지 않은 코드의 문제점
핸드폰 번호를 Key로 사용하는 HashMap을 만든다.
public class Phone {
private String number;
public Phone(String number) {
this.number = number;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Phone)) {
return false;
}
Phone phone = (Phone) o;
return Objects.equals(number, phone.number);
}
}
class Main {
public static void main(String[] args) {
Map<Phone, String> map = new HashMap<>();
Phone phone = new Phone("010-1234-5678");
map.put(phone, "yhh");
System.out.println(map.get(phone)); // yhh
System.out.println(map.get(new Phone("010-1234-5678"))); // null
}
}
equals를 재정의 하여 논리적으로 같은 객체로 만들었지만 해시코드가 다르기때문에 서로 다른 값을 반환한다.
👉 해결방법
최악의 (하지만 적합한) hashCode 구현
@override
public int hashCode() {
return 42;
}
매번 똑같은 해시코드를 반환하지만 모든 객체에 똑같은 값을 반환하므로 연결리스트 처럼 동작한다. (이해하기 어렵다면 해시 해시에 대해 정리해둔 글이나 해시충돌에 대해 공부해보는 것이 좋다.)
전형적인 hashCode 메서드
@Override
public int hashCode() {
return 31 * number.hashCode();
}
31을 곱하는 이유는 홀수이면서 소수이기 때문이다.
만약 숫자가 짝수이고 오버플러우가 발생하면 정보를 잃게된다. 2를 곱하는 것은 시프트 연산과 같은 결과를 내기 때문이다. (❓❓❓)
IDE에서 만들어주는 hashCode
@Override
public int hashCode() {
return Objects.hash(number);
}
31을 곱했던 함수보다 속도가 더 느리다. 배열이 만들어지고 기본 타입이 있다면 박싱과 언박싱을 거쳐야한다. hash() 메서드는 성능에 민감하지 않은 상황에서만 사용하자.
Objects.hash()의 내부 로직
Object 의 hashcode 내부로직
해시 코드를 지연 초기화하는 hashCode 메서드
private String number;
private int hashCode;
@Override
public int hashCode() {
int result = hashCode;
if (result == 0) {
result = number.hashCode();
hashCode = result;
}
return result;
}
👉 정리