[Java] hashCode()
hashCode()
hashCode()는 객체, 즉 Object에 정의되어 있다. hashCode()는 객체의 주소 값을 변환하여 생성한 고유한 정수 값이다.
만약 같은 객체를 참조하고 있다면 hashCode 값은 동일하게 나온다.
예시로, 다음과 같이 작성하여 출력해보면 정수값을 확인해볼 수 있다.
class Person { }
public class Test {
public static void main(String[] args) {
System.out.println(new Person().hashCode()); // ex) 798154996
}
}
Java의 모든 객체의 최상위 부모는 Object이므로 hashCode() 메서드를 Override하여 재정의할 수 있다.
equals()와 hashCode()를 같이 재정의?
아마 Java를 좀 공부했거나 Effective Java를 본 사람은 equals()와 hashCode()를 같이 재정의하라는 말을 들어봤을 것이다.
왜 같이 재정의 하라는 것일까?
먼저 hash와 관련된 Collection인 HashMap을 살펴보자
HashMap은 hashCode를 기준으로 key를 정한다.
그럼 다른 객체라도 hashCode만 같다면 key가 중복이 되는걸까?
다음 코드를 한 번 실행시켜보자.
import java.util.HashMap;
import java.util.Objects;
class Person {
private String name;
public Person(String name) {
this.name = name;
}
@Override
public int hashCode() {
return Objects.hash(name);
}
}
public class Test {
public static void main(String[] args) {
Person p1 = new Person("A");
Person p2 = new Person("A");
System.out.println(p1.equals(p2)); // false
System.out.println(p1.hashCode()); // 96
System.out.println(p2.hashCode()); // 96
HashMap<Person, Integer> hashMap = new HashMap<>();
hashMap.put(p1, 1);
hashMap.put(p2, 2);
System.out.println(hashMap.size()); // 2
System.out.println(hashMap.get(p1)); // 1
System.out.println(hashMap.get(p2)); // 2
}
}
위 코드는 hashCode()를 override하여 name이 같다면 같은 hashCode가 뜨도록 했다.
하지만 hashMap에서는 각자 다른 key로 인식하는 것을 볼 수 있다.
즉 hashMap은 같은 hashCode더라도 다른 객체라면 다른 key로 처리를 해주는 것이다.
Java의 hashMap은 hashCode가 같은 key를 LinkedList로 묶어 처리했었다.
하지만 Java 8 이후 BinaryTree로 바뀌었다. 자세한 내용은 다음 링크를 참고하자.
https://dzone.com/articles/hashmap-performance
그럼 같은 객체지만 hashCode만 다른 경우는 어떻게 될까?
import java.util.HashMap;
import java.util.Objects;
class Person {
private String name;
public Person(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return Objects.equals(name, person.name);
}
}
public class Test {
public static void main(String[] args) {
Person p1 = new Person("A");
Person p2 = new Person("A");
System.out.println(p1.equals(p2)); // true
System.out.println(p1.hashCode()); // 798154996
System.out.println(p2.hashCode()); // 681842940
HashMap<Person, Integer> hashMap = new HashMap<>();
hashMap.put(p1, 1);
hashMap.put(p2, 2);
System.out.println(hashMap.size()); // 2
System.out.println(hashMap.get(p1)); // 1
System.out.println(hashMap.get(p2)); // 2
}
}
위 코드는 equals를 override하여 name이 같다면 true가 뜨도록 하였다.
역시 hashCode가 다르기 때문에 각각 따로 저장되는 것을 볼 수 있다.
같은 key로 처리 되려면 같은 객체(equals()가 true)이면서 hashCode가 같아야 한다는 것을 알 수 있다.
때문에 equals()를 재정의한다면 hashCode()도 같이 재정의하는 것이 좋다.
String에서의 hashCode()
우리가 많이 사용하는 String 객체의 hashCode는 어떻게 될까?
import java.util.HashMap;
public class Test {
public static void main(String[] args) {
String s1 = new String("ABC");
String s2 = new String("ABC");
System.out.println(s1.equals(s2)); // true
System.out.println(s1.hashCode()); // 64578
System.out.println(s2.hashCode()); // 64578
HashMap<String, Integer> hashMap = new HashMap<>();
hashMap.put(s1, 1);
hashMap.put(s2, 2);
System.out.println(hashMap.size()); // 1
System.out.println(hashMap.get(s1)); // 2
System.out.println(hashMap.get(s2)); // 2
}
}
위와 같이 String은 문자열이 같다면 hashCode가 똑같이 뜨는 것을 볼 수 있다.
그럼 어떻게 정의되어 있길래 같은 값이 나오는걸까?
public int hashCode() {
int h = hash;
if (h == 0 && !hashIsZero) {
h = isLatin1() ? StringLatin1.hashCode(value)
: StringUTF16.hashCode(value);
if (h == 0) {
hashIsZero = true;
} else {
hash = h;
}
}
return h;
}
인코딩 타입마다 다르게 정의되어 있다. 여기선 Latin1의 hashCode()를 살펴보자
그럼 다음과 같이 구현되어 있는 것을 확인할 수 있다.
public static int hashCode(byte[] value) {
int h = 0;
for (byte v : value) {
h = 31 * h + (v & 0xff);
}
return h;
}
보면 한 글자마다 31씩 곱하여 더하는 것을 볼 수 있다.
즉, 객체의 주소 값을 기준으로 hashCode를 생성하지 않기 때문에 문자열이 같으면 같은 hashCode값이 나오는 것이다.
* 왜 31인가?
https://github.com/dolly0920/Effective_Java_Study/issues/25
위 글을 통해 자세히 살펴볼 수 있다. 홀수이며 소수인 수를 관행적으로 사용한 것 같다.
이렇게 hashCode에 대해서 살펴보았다.
만약 equals()를 재정의 할 일이 있다면, 꼭 hashCode()도 같이 재정의 해주도록 하자
내가 추천하는 방식은 Lombok의 @EqualsAndHashCode를 사용하는 것이다.
'Language > Java' 카테고리의 다른 글
[Java] 제네릭(Generic) (0) | 2024.03.12 |
---|---|
[Java] Mockito (0) | 2023.07.31 |
[Java] Java 버전 별 특징 (2) | 2023.04.14 |
[Java] Java 11 특징 (0) | 2023.04.13 |
[Java] Java 9 특징 (0) | 2023.04.13 |
댓글
이 글 공유하기
다른 글
-
[Java] 제네릭(Generic)
[Java] 제네릭(Generic)
2024.03.12 -
[Java] Mockito
[Java] Mockito
2023.07.31 -
[Java] Java 버전 별 특징
[Java] Java 버전 별 특징
2023.04.14 -
[Java] Java 11 특징
[Java] Java 11 특징
2023.04.13