Skip to content

Commit

Permalink
item-11 equals 를 재정의하려거든 hashCode 도 재정의하라
Browse files Browse the repository at this point in the history
  • Loading branch information
Lokie89 committed Jul 6, 2020
1 parent 74cb79b commit c536d82
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 0 deletions.
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -605,7 +605,47 @@ public class HashMap<K,V> extends AbstractMap<K,V>
}
}
```

# item-11 equals 를 재정의하려거든 hashCode 도 재정의하라
#### 정리
equals 를 재정의한 클래스 모두에서 hashCode 도 재정의해야 한다.

equals 와 hashCode 에 대한 Object 명세의 규약
- equals 비교에 사용되는 정보가 변경되지 않았다면, 애플리케이션이 실행되는 동안
그 객체의 hashCode 메서드는 몇 번을 호출해도 일관되게 항상 같은 값을 반환해야 한다.
- equals(Object) 가 두 객체를 같다고 판단했다면, 두 객체의 hashCode 는 똑같은 값을 반환해야 한다.
- equals(Object) 가 두 객체를 다르다고 판단했더라도, 두 객체의 hashCode 가 서로 다른 값을 반환할 필요는 없다.

hashCode 를 재정의하지 않으면 논리적 동치인 두 객체가 서로 다른 해시코드를 반환한다.
HashMap 은 해시코드가 다른 엔트리끼리는 동치성 비교를 시도조차 하지 않도록 최적화되어 있기 때문에 재정의 되지 않은 hashCode 를 갖는
객체는 같은 객체라 판단하더라도 리턴값이 달라질 수 있다.

좋은 hashCode 를 작성하는 요령
1. int 변수 result 를 선언한 후 값 c 로 초기화 한다. 이 때 c 는 첫 번째 핵심 필드를 2.a 방식으로 계산한 해시코드
2. 해당 객체의 나머지 핵심 필드들에 아래 작업들을 수행한다.
a. 해당 필드의 해시코드 c 를 계산한다.
- 기본 타입 필드이면 박싱 클래스의 hashCode 메서드 수행
- 참조 타입 필드면서 이 클래스의 equals 메서드가 이 필드의 equals 를 재귀적으로 호출해 비교한다면,
이 필드의 hashCode 를 재귀적으로 호출
- 배열 타입 필드라면 핵심 원소 가각ㄱ을 별도 필드처럼 다룬다. 모든 원소가 핵심 원소이면 Arrays.hashCode 메서드를 사용한다.
b. 위의 단계에서 계산한 해시코드 c 로 result 를 갱신한다.
- result = 31 * result + c;
3. result 를 반환한다.
위의 hashCode 작성 방법중 result 를 갱신하는 부분 ( 2.b ) 에서 곱셈을 해주는 이유는
앞의 코드에 따라 변화를 다르게 하기 위함이다. 예를 들어 곱셈 부분이 없다면
String field1 = "a", String field2 = "b" 인 객체는
String field1 = "b", String field2 = "a" 와 같은 해시코드를 갖게 되기 때문이다.
곱할 숫자를 31로 정한 이유는 홀수이면서 소수이기 때문이다.
만약 이 숫자가 *짝수이고 오버플로가 발생한다면 정보를 잃게 된다.
소수를 곱하는 이유는 명확하지 않지만 전통적으로 그리 해왔다.

다른 방법으로 Objects 클래스의 hashCode 를 계산해주는 정적 메서드인 hash 가 있다.
한줄로 보기 쉽게 작성할 수 있는 이점이 있으나 속도가 더 느리다.

클래스 불변이고 해시코드를 계산하는 비용이 크다면, 캐싱하는 방식을 고려해야 한다.
#### 내용 추가

# item-1
#### 정리
Expand Down
60 changes: 60 additions & 0 deletions src/item11/PhoneNumber.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package item11;

import java.util.Objects;

/**
* @author lokie on 2020/07/06
*/
public final class PhoneNumber {
private final short areaCode, prefix, lineNum;

public PhoneNumber(int areaCode, int prefix, int 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 obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof PhoneNumber)) {
return false;
}
PhoneNumber pn = (PhoneNumber) obj;
return pn.lineNum == lineNum && pn.prefix == prefix
&& pn.areaCode == areaCode;
}

private int hashCode;

@Override
public int hashCode() {
// hashCode 작성법
// int result = Short.hashCode(areaCode);
// result = 31 * result + Short.hashCode(prefix);
// result = 31 * result + Short.hashCode(lineNum);
// return result;

// Objects 클래스가 제공하는 hashCode 반환 메서드
// return Objects.hash(areaCode, prefix, lineNum);

// 해시코드 캐싱
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;
}
}
26 changes: 26 additions & 0 deletions src/item11/Test.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package item11;

import java.util.HashMap;
import java.util.Map;

/**
* @author lokie on 2020/07/06
*/
public class Test {
static Map<Integer, String> map = new HashMap();
public static void a(){
Integer a = new Integer(150);
System.out.println(System.identityHashCode(a));
map.put(a,"150");
}

public static void main(String[] args) {
a();
Integer a = new Integer(150);
Integer b = new Integer(150);
System.out.println(System.identityHashCode(b));
System.out.println(map.get(b));
System.out.println(a == b);
System.out.println(a.equals(b));
}
}

0 comments on commit c536d82

Please sign in to comment.