...
객체의 hashCode는 고유하지 않다
자바에서는 포인터를 철저히 숨겼기 때문에 직접적인 객체의 주소 원래값을 얻을수는 없다. 그래서 자바에서는 참조 변수의 주소값을 표현하기 위해 위와 같이 해시코드를 이용한다.
보통 해싱 알고리즘 상 서로 다른 두 주소값을 가지고 있는 객체는 결코 같은 해시코드를 가질 수 없다. 하지만 예외가 있는데, 바로 hashCode() 메서드가 int형 정수를 반환한다는 점이다.
만일 자신의 컴퓨터가 32bit 사양이라면 이는 문제가 되지 않는다.
하지만 현대 시대에서 대부분 사용하는 64비트 컴퓨터에서 돌아가는 JVM(가상머신)은 기본적으로 8바이트(64bit) 주소체계를 기본으로 하는데, 만일 8바이트의 주소값을 hashCode를 이용해 반환하면 메서드의 타입에 따라 4바이트(32bit)로 강제 캐스팅(long → int)이 되기 때문에 값이 겹칠수도 있다는 케이스가 존재하게 된다. (이를 Hash Collisions 해시충돌이라 일컫는다)
그렇다면 hashCode 메서드의 반환 타입을 long 타입으로 반환되게 메소드를 업그레이드 하지 않은 이유는, 기존에 int 타입형 메서드로서 여러 프로젝트에서 사용되고 있기 때문에 어쩔수 없이 호환성을 위해서 놔둔것으로 보면 된다.
객체의 hashCode 중복 확인해보기
다음은 서로 다른 객체일지라도 같은 해시코드를 반환할수 있다는 점을 확인하기 위한 예제 코드이다.
딕셔너리 형태의 자료형을 구현하기 위해 HashMap 클래스를 이용하였다.
루프문에서 먼저 값(value)으로 들어갈 새로 객체를 생성 하고, 키(key)로 들어갈 생성한 객체의 해시코드를 얻는다. 그리고 해당 해시코드 정수값이 HashMap에 이미 들어있는지 아닌지 검사를하고, 동일한 해시 키값을 가진 데이터가 있으면 콘솔을 출력한다.
import java.util.HashMap;
import java.util.Map;
public class Test1 {
public static void main(String[] args) {
Map<Integer, Object> objectMap = new HashMap<>(); // {해시코드 : 객체} 형태의 Map 자료형
int j = 1;
// int형 범위(21억번) 돌림
for (long i = Integer.MIN_VALUE; i < Integer.MAX_VALUE + 1L; i++) {
Object obj = new Object(); // 객체 생성
int hashCode = obj.hashCode(); // 해시코드 얻기
// Map에 해당 해시 코드가 존재하는지 확인 (만일 있다면 출력)
Object obj2 = objectMap.get(hashCode);
if (obj2 != null) {
System.out.println((i - Integer.MIN_VALUE + 1L) + "번째 루프");
System.out.println("새롭게 만든 객체 해시코드 : " + obj.hashCode());
System.out.println("기존에 있는 객체 해시코드 : " + obj2.hashCode());
System.out.println("해시코드가 같아도 obj2 == obj 는 : " + (obj2.equals(obj))); // 해시코드가 같아도 객체의 주솟값은 원래 다르니 false 출력
System.out.println("");
if (j >= 4) return; // 4번만 출력
j++;
} else {
objectMap.put(hashCode, obj); // Map에 해시코드와 객체 등록
}
}
}
}
결과는 보다시피 64비트 컴퓨터에서의 자바 프로그래밍에선, 메서드 정의상 서로 다른 객체라도 같은 해시코드를 가지고 있을 수 있다는 점을 알 수 있다. (결과가 너무 많아 일부러 4번만 출력하도록 제한하였다)
그러면 문제가 있는 것이 아닌가?
다행히도 이러한 예외 케이스가 있다고 해도, 자바 프로그래밍에는 큰 문제가 되지 않는다. 왜냐하면 보통 객체를 비교할 일이 생길경우, Object 클래스의 hashCode() 와 equals() 메서드를 오버라이딩하여 사용할텐데, 두 객체를 비교하는 과정에서 만일 두 객체의 해시 코드가 같으면 바로 equals() 메서드를 통해 두 객체의 진짜 주소를 직접적으로 비교하도록 구성 되어 있기 때문이다.
다만 해시코드 값으로만 두 객체의 동등을 판별하려 할때 이러한 예외적인 케이스가 있다는 정도만 알고만 있자.
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.