(Effective Java 2/E) 111. Item 9 - Always override hashCode when you override equals
Always override hashCode when you override equals
- 2판 제목 : equals를 재정의할 때는 반드시 hashCode도 재정의하라
- 3판 제목 : equals를 재정의하려거든 hashCode도 재정의하라
equals를 재정의한 클래스의 많은 버그가 hashCode에서 발생한다.
hashCode 재정의는 equals의 재정의 규약에도 포함되어있는 만큼 누락될 시 HashSet이나 HashMap과 같은 컬렉션에서 문제를 일으킬 수 있다.
좀 더 자세히 본다면 아래와 같이 명세할 수 있다.
- 애플리케이션의 실행 중 같은 객체의
hashCode를 여러 번 호출하는 경우에, equals가 사용하는 정보들이 변경되지않았다면 항상 같은 값을 반환해야 한다.
equals(Object) 메서드가 같다고 판정한 두 객체의 hashCode 값은 항상 같아야 한다.
equals(Object) 메서드가 다르다고 판단한 두 객체의 hashCode 값은 항상 다를 필요는 없다. 하지만 HashTable의 성능을 생각하는 경우 다른 값을 가지는 것이 좋다.
따라서 equals를 재정의한 클래스는 반드시 hashCode도 재정의해야한다.
에제를 살펴보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public final class PhoneNumber { private final short areaCode, prefix, lineNum;
public PhoneNumber(short areaCode, short prefix, short lineNum){ this.areaCode = rangeCheck(areaCode, 999, "지역코드"); this.prefix = rangeCheck(prefix, 999,"프리픽스"); this.lineNum = rangeCheck(lineNum, 9999, "가입자번호"); } private static short rangeCheck(int val, int max, String arg){ if(val < 0 || val > max){ throw new IllegalArgumentException(arg+" : "+val); } return (short) val; }
@Override public boolean equals(Object o){ if(o == this) return true; if(!(o instanceof PhoneNumber)){ return false; }
PhoneNumber pn = (PhoneNumber) o; return pn.lineNum == lineNum && pn.prefix == prefix && pn.areaCode == areaCode; }
}
|
PhoneNumber 클래스를 HashMap에 넣어서 사용한다고 가정해보자.
1 2
| Map<PhoneNumber, String> map = new HashMap<>(); map.put(new PhoneNumber((short) 707,(short) 867,(short) 5309), "Jenny");
|
이후 아래 코드를 출력하면 어떤 값이 나올까?
1
| System.out.println(map.get(new PhoneNumber((short) 707,(short) 867,(short) 5309)));
|
기대값은 “Jenny” 겠지만 사실 null이 출력된다.
이는 PhoneNumber 클래스에 hashCode를 재정의하지 않았기 때문이다.
반대로 hashCode를 재정의하면 문제는 해결된다.
재정의하는 코드는 아래와 같다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| private volatile int hashCode;
@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; }
|
주의해야할 것은 성능을 개선하기 위해 객체의 중요 부분을 해시코드 계산 과정에서 생략하면 안된다는 것이다.