brunch

You can make anything
by writing

C.S.Lewis

by 서준수 Mar 27. 2020

자바 hashCode()

자바의 hashCode()

hashCode()는 객체의 hashCode를 리턴한다. hashCode일반적으로 객체의 주소값을 변환하여 생성한 객체의 고유한 정수값이다. 따라서 두 객체가 동일 객체인지 비교할 때 사용할 수 있다.


객체의 hashCode()

equals() 글에서 봤던 예제에서 hashCode를 출력하면 다음과 같다. 결과는 메모리에 할당될 때 주소값이 바뀌면 아래 결과와 다른 값이 나오겠지만 person2와 person3이 동일한 값을 갖는 것은 변하지 않는다.

각 객체의 주소가 위와 같다고 가정하면 person1과 person2의 hashCode는 당연히 다르다. hashCode는 주소값을 기반으로 생성된 정수값이기 때문이다. 대신 person2와 person3은 주소값이 같기 때문에 hashCode도 동일하다.


String의 hashCode()

String은 재정의한 equals()에서 각 문자열에서 한 글자씩 비교하는 방식을 보였다. hashCode() 또한 독특하게 재정의되어 있다.

문자열에서 한 글자씩 가져와서 정수값으로 변경하고 있다. 한 글자를 가져와서 정수와 더하면 해당 글자의 ascii 코드의 값을 사용한다.


31을 곱하는 이유는 홀수이기 때문이다. 짝수를 곱했을 때 오버플로우 되면 정보 손실이 발생할 수 있기 때문이다. 이진수에서 2를 곱하면 비트가 왼쪽으로 한 비트씩 이동하기 때문이다. 홀수 중 31의 장점은 31 * i 이 (i<<5) - i와 같기 때문에 곱셈 대신 비트 이동 및 뺄셈으로 처리하여 성능면에서 좀 더 이득이 있기 때문이다. 그러나 요즘 VM은 자동으로 최적화한다.


결론적으로 주소값을 기준으로 정수값의 hashCode를 생성하는 것이 아니다. 결국 서로 다른 String 객체도 문자열이 같으면 hashCode가 같은 것이다.


다음 예제를 보자.

Line 6~7 : 동일한 hello라는 문자열을 가진 str1과 str2의 hashCode는 같다.

Line 8 : str3은 world라는 문자열을 가졌기 때문에 str1과 str2와는 다른 hashCode를 가진다.

Line 12~13 : str4와 str5는 서로 주소값이 다른 객체임에도 불구하고 hashCode가 같다. 동일한 문자열인 abc를 가졌기 때문이다.



hashCode는 유일한 값이라고 했지만 String에서 중복 가능성이 있다. String의 hashCode() 메서드를 보면 31 * h + ascii값 연산을 하기 때문에 특정 문자열 조합에서 동일한 정수값을 가지는 경우가 있다.


관련 예제는 이곳의 코멘트에 있다. 아래 두 String은 hashCode가 같다.


String a = "Z@S.ME";

String b = "Z@RN.E";



hashCode()와 Hash 관련 Collections

hashCode가 객체의 유일한 값이 아니라면 Hash와 관련된 collections에서 문제가 되지 않을까?

hashMap을 대표적으로 생각해보자. hashMap에서 key는 hashCode를 기준으로 정해진다. 근데 만약 서로 다른 객체의 hashCode가 같다면 중복 key가 되는 것이다. Map의 기본 특징으로 key는 중복이 되지 않는다.

Map의 key는 중복을 허용하지 않는다.

그러나 다행히 hashMap은 서로 다른 객체의 같은 hashCode에 대해서도 적절히 잘 처리해주도록 구성되어 있다. 따라서 만약 key로 앞선 String a와 String b를 사용하더라도 중복 문제가 없는 것이다. String a와 String b가 hashCode는 같지만 equals()의 결과가 false이고 사용자 입장에서는 직관적으로 서로 다른 문자열인 것을 인지할 수 있다. 아마 이런 경우를 고려한 것이 아닐까 싶다.


근데 만약 반대의 경우라면? 즉 equals()의 결과도 true이고 사용자가 봤을 때도 같은 형태를 하고 있지만 hashCode는 false인 다른 객체라면 어떻게 해야 할까? 사용자는 hashCode를 확인하기 전까지는 아마 동일한 key라고 생각하고 사용할 수 있을 것이다. 하지만 hashCode가 다르면 동일한 key가 아니다.


따라서 equals()와 hashCode()가 모두 true이면서로 같은 key로 취급다는 기준을 세우면 사용자 혼란을 막을 수 있다. 이 말은 equals()가 true이면 hashCode()도 true가 될 수 있도록 재정의 하자는 것이다.


다음 예제를 보자.

Line 10~15 : a, b가 hashCode는 같지만 hashMap에서 다른 key로 구분해주고 있다. equals()가 false인 것에 주목할만하다.

Line 23~28 : equals()가 true이기 때문에 자칫 같은 key라고 생각할 수 있다. 하지만 두 객체의 hashCode가 서로 다르기 때문에 동일한 key가 아니다. 따라서 hashMap의 크기가 2가 된다.


그러면 equals()가 true일 때는 동일한 key로 인하도록 하려면 어떻게 해야할까? hashCode()를 적절히 재정의하여 동일한 hashCode를 가질 수 있게 하면 된다.

Line 35 : hashCode()를 정의 하였다. 이제 Person 객체의 name이 동일한 문자열이면 같은 hashCode를 갖게 된다.

Line 15 : 따라서 key person1, person2를 사용하여 두 번 put해도 덮어쓰기가 되어 hashMap의 크기는 1이 된다.


결국 String 클래스처럼 된 형태이다. 서로 다른 객체이지만 equals()에서 어떤 기준(ex. 문자열)에 의한 결과가 동일하도록 재정의하고, hashCode()에서도 해당 기준을 기반으로 재정의하면 된다.


즉 동일한 key는 equals()와 hashCode() 모두 같은 경우이다.


이런 상황을 대비하기 위해서 equals() 재정의 시에 hashCode()도 같이 재정의 하는 것이 좋다.


요약)

equals()가 false이고 hashCode()가 true인 경우 => hashMap에서 다른 key로 처리

equals()가 true이고 hashCode()가 false인 경우 => hashMap에서 다른 key로 처리

equals()가 true이고 hashCode()가 true인 경우 => hashMap에서 같은 key로 처리


ref.)

hashCode의 31을 곱하는 이유 : https://stackoverflow.com/questions/299304/why-does-javas-hashcode-in-string-use-31-as-a-multiplier

String의 hashCode 중복 문제 : https://blog.ggaman.com/916

hashCode 재정의 : https://nesoy.github.io/articles/2018-06/Java-equals-hashcod

브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari