equals 함수를 재정의하는 클래스는 반드시 hashCode 함수도 재정의 해야 한다.
그렇지 않으면 Object.hashCode의 일반 규약을 어기게 되므로, HashMap, HashSet, Hashtable같은 해시(hash) 기반 컬렉션과 함꼐 사용하면 오동작하게 된다.
Object 클래스 명에서 복사해 온 일반 규약은 다음과 같다.
응용프로그램 실행 중에 같은 객체의 hashCode를 여러 번 호출하는 경우 equals가 사용하는 정보들이 변경되지 않았다면 언제나 동일한 정수(integer)가
반환되야 한다.
다만 프로그램이 종료되었다가 다시 실행되어도 같은 값이 나올 필요는 없다.
equals(Object) 함수가 같다고 판정한 두 객체의 hashCode 값은 같아야 한다.
equals(Object) 함수가 다르다고 판정한 두 객체의 hashCode 값은 꼭 다를 필요는 없다. 그러나 서로 다른 hashCode 값이 나오면 해시 테이블(hashtable)의 성능이
향상될 수 있다는 점은 이해하고 있어야 한다.
hashCode를 재정의하지 않으면 위반되는 핵심 규약은 두 번째이다. 같은 객체는 같은 해시 코드 값을 가져야 한다는 규약이 위반되는 것이다.
equals 함수가 논리적으로 같다고 판단한 두 객체라 해도 Object의 hashCode 입장에서 보면 그다지 공통점이 없는 두 객체일 뿐이다.
따라서 Object의 hashCode 함수는 규약대로 같은 정수를 반환하는 대신, 무작위로 선택된 것처럼 보이는 두개의 정수를 반환한다.
예를 들어, 아래의 간단한 PhoneNumber 클래스를 보자. equals 함수는 규칙 8 지침대로 구현되어 있다.
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 29 30 31 32 33 34 35 36 37 38 39 | public final class PhoneNumber { private final short areaCode; private final short prefix; private final short lineNumber; public PhoneNumber(int areaCode, int prefix, int lineNumber){ rangeCheck(areaCode, 999, "area code"); rangeCheck(prefix, 999, "prefix"); rangeCheck(lineNumber, 9999, "line number"); this.areaCode = (short)areaCode; this.prefix = (short)prefix; this.lineNumber = (short)lineNumber; } private static void rangeCheck(int arg, int max, String name){ if(arg < 0 || arg > max){ throw new IllegalArgumentException(name + ": " + arg); } } @Override public boolean equals(Object o){ if(o == this){ return true; } if(!(o instaceof PhoneNumber)){ return false; } PhoneNumber pn = (PhoneNumber)o; return pn.lineNumber == lineNumber && pn.prefix == prefix && pn.areaCode == areaCode; } //hashCode 함수가 없으므로 문제가 발생한다!! //... 이하 ㅐㅇ략 } | cs |
1 2 3 4 | //가장 끔찍한 형태의 해시 함수, 절대로 이렇게 구현하지 말 것. @Override public int hashCode() { return 42; } | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | //초기화 지연 기법을 사용해 해시 코드 캐싱 private volatile int hashCode; //(규칙71 참조) @Override public int hashCode() { int result = hashCode; if(result == 0) { result = 17; result = 31 * result + areaCode; result = 31 * result + prefix; result = 31 * result + lineNumber; hashCode = result; } return result; } | cs |
다음과 같이 되어 있는 클래스의 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.Enum.ClassName; int multiplication = hashCode.Enum.ClassMultiplication; result = multiplication * result + areaCode; result = multiplication * result + prefix; result = multiplication * result + lineNumber; this.hashCode = result; return result; } | cs |
'Java' 카테고리의 다른 글
인텔리제이 스프링부트 오류 (0) | 2020.07.01 |
---|---|
인텔리제이 세팅 (0) | 2020.07.01 |
Effective Java #08 equals를 재정의할 때는 일반 규약을 따르라 (0) | 2016.04.17 |
Effective Java #07 종료자 사용을 피하라 (0) | 2016.04.13 |
Effective Java #06 유효기간이 지난 객체 참조는 폐기하라 (0) | 2016.04.13 |