서론
우리가 임의의 Class를 생성했을 때 별도로 생성하지 않아도 사용할 수 있는 메서드들이 있다.
그 예가 바로 toString(), equals(), hashCode()이다.
이러한 메서드들을 별도로 생성하지 않아도 사용할 수 있는 이유는 Java의 모든 class들은 Object 클래스를 상속받기 때문이다.
즉, 위 메서드들은 Object 클래스의 메서드이다.
참고) == 연산자는 대체 무엇인가?
기본적으로 == 연산은 주소값을 비교해서 같으면 true, 다르면 false를 반환한다.
hashCode()
hashCode 메서드는 객체의 해시값(integer)을 반환한다.
가장 상위 객체인 Object 클래스의 hashCode()는 아래와 같이 native 메서드로 지정되어 있으며, 객체의 메모리 주소를 반환한다.
@HotSpotIntrinsicCandidate
public native int hashCode();
또한, 따로 재정의를 하지 않으면 Object의 hashCode와 System.identityHashCode()는 같은 값을 반환한다.
public static void main(String[] args) {
DummyClass a = new DummyClass("dummy");
System.out.println(System.identityHashCode(a));
System.out.println(a.hashCode());
}
private static class DummyClass {
private String content;
public DummyClass(String content) {
this.content = content;
}
}
따라서 위 코드를 실행하면 두 명령 모두 같은 값을 반환한다.
equals()
Object 클래스의 equals()를 찾아가보면 아래와 같다
public boolean equals(Object obj) {
return (this == obj);
}
기본적으로, Object의 equals()는 주소값을 비교해서 같으면 true를 반환한다.
그렇다면 hashCode는 memory 주소를 반환하고, equals는 메모리 주소가 같은지 확인하는 메서드인가?
아니다. 이는 Object의 default 구현일 뿐 이를 상속 받는 Class 에서 정의하기 나름이다.
그럼 대체 어떻게 재정의 해야하는 건데?
hashCode()를 정의할 때는 다음과 같은 규약이 존재한다.
1. 변경되지 않은 한 객체의 hashCode 메서드를 호출한 결과는 항상 동일한 integer값이어야 한다.
- 객체가 변경되었더라도 equals 메서드가 참고하는 정보가 변경되지 않았다면 hashCode 값은 달라지지 않는다.
2. equals 메서드가 같다고 판별한 두 객체의 hashCode 결과는 같은 값이어야 한다.
3. but, equals 메서드가 다르다고 판별한 두 객체의 hashCode값이 반드시 달라야 하는 것은 아니다.
- 단, 같지 않은 객체들이 각기 다른 hashCode 값을 가지면 해시 테이블 성능이 향상된다.
equals | hashCode | |
규약1 | true | 같아야 한다 |
규약2 | false | 같아도 되고, 달라도 된다 |
IDE에서 자동 완성으로 만들어지는 equals와 hashCode
equals의 경우 먼저 메모리 주소를 확인하고 같으면 바로 true를 반환한다. 만약 메모리 주소가 다르더라도 각 attribute값들이 같으면 true를 반환한다.
hashCode의 경우 각 attribute들의 값을 해시함수를 통해 변환한 값을 반환한다. 즉, attribute 값이 같다면 hashCode의 값도 같을 것이다.
사실 IDE의 자동완성으로 equals와 hashCode를 생성하면 위에서 언급한 규약들을 다 지킬 수 있다.
그럼 이 equals()와 hashCode()는 알아서 어디에 사용하는건데?
HashMap, HashSet 등 hash를 사용하는 class들은 기본적으로 hashCode를 기반으로 데이터를 조회한다. (key = hashCode())
위 메서드를 잘 보면 hashCode로 바로 index를 결정하는 것이 아니라, hashCode를 Entry<>tab[] 이라는 데이터를 담는 그릇의 길이로 mod 연산을 진행한 값을 index로 사용한다. 이는 간혹, hashCode는 다르나, index는 같아질 수 있는 확률이 존재한다는 것을 의미한다. 또한 이러한 경우를 바로 Hash Collision(해시 충돌)이라고 한다.
Hash 충돌시 대응
해시 충돌 발생시 HashTable은 해당 index에 LinkedList형태로 각 value들을 저장한다. 또한 조회시에 해당 index에 하나 이상의 데이터가 있을 시 각 value들의 equals() 메서드를 확인하여 원하는 객체를 조회한다. LinkedList 형태이기 때문에 조회 속도는 O(n)이 될 것이다.(충돌났을 때에만, 안나면 O(1)).
결론
1. hash를 사용하는 class 에서 조회시 hashCode()를 사용한다.
2. hashCode가 달라도 같은 index에 들어가는 경우가 생긴다. (해시충돌)
3. 같은 index에 여러개 들어있으면 equals를 통해 검사 ( O(n))
4. equlas를 재정의할 때에는 반드시 hashCode를 재정의 해야함