equals를 재정의하려거든 hashCode도 재정의 해라
equals를 재정의한 클래스 모두 hashCode를 재 정의 해야한다.
재 정의 하지않으면 hashCode 일반 규약을 어기게 되는거고 HashMap이나 HashSet을 사용할 때 문제가 발생할 수 있다.
자바 문서에 Object 객체 hashCode 메서드에는 이런식으로 명시 되어있다.
- equals비교에 사용되는 정보가 변경되지 않았다면, 어플리케이션 실행 중 hashCode를 몇번 호출하더라도 동일한 값을 반환 해야한다.
- equlas(Object)가 두 객체를 같다고 판단하면, 두 객체의 hashCode는 똑같은 값을 반환해야 한다.
- eqauls(Object)가 두 객체를 다르다고 판단했을때도 다른 hashCode를 반환 할 필요는 없다. 단 다른 객체에 대해서는 다른 값을 반환해야 해쉬 테이블 성능이 좋아진다.
hashCode 재 정의 안했을때 문제
equals를 재 정의 함으로써 필드값이 같다면 두 객체는 동일한 객체라고 판단하는 코드에서는 HashMap과 같이 Collection의 원소로 사용할때는 문제가 발생한다.
class Ab {
private int age;
private String name;
Ab(int age, String name) { this.age = age; this.name = name; }
@Override
public boolean equals(Object o ) {
if(!(o instanceof Ab)) return false;
if( o == this ) return true;
else {
return this.age == ((Ab)o).age && this.name == ((Ab)o).name;
}
}
}
import java.util.*;
public class Main {
public static void main(String[] args) {
Map<Ab, String> map = new HashMap<>();
System.out.println(new Ab(20, "제니").equals(new Ab(20, "제니"))); // true
map.put(new Ab(20, "제니"), "제니");
System.out.println( map.get(new Ab(20, "제니")));// null
}
}
HashMap에서는 get할때 hashCode값을 이용해 Entrty를 찾아오고 추가적으로 equals 연산을 하여 동일 하다면 같은 Entry라 판단하지만, 위 코드 같은 경우에는 hashCode값 자체가 다르기 떄문에 null이 출력된다.
equals를 재 정의 하였다면 hashCode도 반드시 재 정의 해줘야한다.
class Ab {
private int age;
private String name;
Ab(int age, String name) { this.age = age; this.name = name; }
@Override
public boolean equals(Object o ) {
if(!(o instanceof Ab)) return false;
if( o == this ) return true;
else {
return this.age == ((Ab)o).age && this.name == ((Ab)o).name;
}
}
@Override
public int hashCode() {
int result = 31 * Integer.hashCode(age);
result = 31 * result + name.hashCode();
return result;
}
}
import java.util.*;
public class Main {
public static void main(String[] args) {
Map<Ab, String> map = new HashMap<>();
System.out.println(new Ab(20, "제니").equals(new Ab(20, "제니"))); // true
System.out.println(new Ab(20, "제니").hashCode() == (new Ab(20, "제니")).hashCode()); // true
map.put(new Ab(20, "제니"), "제니");
System.out.println( map.get(new Ab(20, "제니")));// null
}
}
hashCode 작성시 주의점
- 동치인 인스턴스가 서로 다른 해시코드를 반환한다면 원인을 찾아 해결하기
- 다른 필드로부터 계산해낼 수 있는 필드인 파생 필드는 해시코드 계산에서 제외하자
- equals 비교에 사용되지 않는 필드는 반드시 제외 해야한다.
- 성능을 높인답시고 핵심 필드를 생략해서는 안된다. ( 해시 품질이 낮아질 수 있음 )
- hashCode가 반환하는 값의 생성 규칙을 API 사용자에게 공표하지말기 ( 클라이언트가 이 값에 의지하지 않게되고, 추후에 계산방식 변경 가능 )
Objects.hash() 사용하기
Objects 클래스에 hash 메서드는 임의의 개수만큼 객체를 받아 해시코드를 계산해주는 정적 메서드다.
이 메서드를 활용하면 단 한줄로 hashCode를 계산할 수 있다는 장점을 가지고 있지만 느리다는 단점도 가지고 있다.
class Ab {
private int age;
private String name;
Ab(int age, String name) { this.age = age; this.name = name; }
/** **/
@Override
public int hashCode() {
return Objects.hash(age,name);
}
}
hashCode 캐싱 전략
클래스가 불변이고 해시코드를 계산하는 비용이 크다면, 매번 새로 계산 하는것보다는 캐싱하는 방식을 고려해야한다.
private int hashCode; // 자동으로 0으로 초기화된다.
@Override public int hashCode() {
int result = hashCode;
if (result == 0) {
result = Short.hashCode(areaCode);
result = 31 * result + Short.hashCode(prefix);
result = 31 * result + Short.hashCode(lineNum);
hashCode = result;
}
return result;
}
'book review > Effective java' 카테고리의 다른 글
[Effective Java] Item 13. clone 재정의는 주의해서 진행하라 (0) | 2024.07.01 |
---|---|
[Effective Java] Item 12. toString을 항상 재 정의하라 (0) | 2024.04.11 |
[Effective Java] Item 10. equals는 일반 규약을 지켜 재 정의하라 (0) | 2024.03.11 |
[Effective Java] Item 9. try-finally보다는 try-with-resources를 사용하라 (0) | 2024.02.14 |
[Effective Java] Item 8. finalizer와 cleaner 사용을 피하라 (0) | 2024.02.13 |