본문 바로가기
언어/JAVA

[JAVA] 객체 비교 시 equals()와 hashCode()는 왜 같이 사용해야 할까??

by chan10 2021. 3. 26.

equals()hashCode()

 hashCode() : 두 객체가 같은 객체인지, 동일성(identity)를 비교하는 메소드

equals() : 두 객체의 내용이 같은지, 동등성(equality)를 비교하는 메소드

equals()는 객체의 주소를 비교하여 객체의 주소가 서로 동일한 경우 동등하다고 인식합니다. , 오직 자기 자신과만 같다고 인식합니다. 그렇기에 @Override로 재정의 하여 비교 대상을 재정의 함으로써 객체의 비교가 가능하고 MapkeySet의 원소로 사용할 수 있습니다.

 

본문

보통 객체의 동등성 비교를 위해 equals()오버라이딩을 하면 hashCode()도 같이 오버라이딩 하라고 하는데 이에 대한 이유를 알아보려고 합니다.

 

HashSet에 객체를 add하면 HashSetadd메서드는 HashMapput메서드를 호출합니다.

HashMapkey값으로 add한 객체를 넣어 호출합니다.

 

그리고 put메서드는 putVal메서드를 호출하는데 첫번째 인자 값으로 hash(key)를 받습니다.

 

hash메서드를 보면 바로 여기서 return값으로 hashCode() 메서드를 호출하게 됩니다.

다형성에 의해 만약 key객체 즉, 처음 HashSetadd한 객체에서 hashCode() 오버라이딩을 해주었다면 오버라이딩된 hashCode()메서드가 호출되고 오버라이딩이 정의되어 있지 않다면 부모(상속받지 않는 경우 Object)에 정의되어 있는 hashCode() 메서드를 호출하게 됩니다.

 

 ObjecthashCode()메서드는 객체가 서로 다르다고 해서 항상 다른 값을 가진다는 보장이 없으며 같은 해시 값을 갖게 될 수도 있습니다. 이를 알기 위해서는 HashTable의 동작 원리를 알아야합니다.

 

Java HashTable

HashTable<key,value>형태로 데이터를 저장합니다. 이 때 hashCode()를 이용하여 key값을 기준으로 고유한 식별값인 해시값을 만듭니다. 이 해시값을 버킷(Bucket)에 저장합니다. 그러나 HashTable의 크기는 한정적이기에 서로 다른 객체라도 같은 해시값을 갖게 될 수도 있습니다. 이것을 해시 충돌(Hash Collisions)이라고 합니다. 이런 경우 해당 버킷에 LinkedList형태로 객체를 추가합니다.

이미지출처 클릭

이렇게 같은 해시값의 버킷안에 다른 객체가 있는 경우 equals()메서드가 사용됩니다.

o HashTableput메서드로 객체를 추가하는 경우

-       값이 같은 객체가 이미 있다면(equals() true) 기존 객체를 덮어씁니다.

-       값이 같은 객체가 없다면(equals() false) 해당 entry LinkedList에 추가합니다.

 

o HashTable get 메서드로 객체를 조회하는 경우

-       값이 같은 객체가 있다면 (equals() true) 그 객체를 리턴합니다.

-       값이 같은 객체가 없다면(equals() false) null을 리턴합니다.

 

본문

돌아와서 put()메서드의 hash()메서드 호출을 통해 얻은 keyhash값을 putVal()메서드의 인자 값으로 넣어 호출합니다. putVal()메서드는 전달받은 keyhash값을 이용해 <key, value>값을 버킷에 분배하게 됩니다.

(연산을 통해 나온 값의 위치가 버킷에 저장되어 있지 않으면 추가)

 

그렇기에 hashCode() 오버라이딩을 하지 않은 경우 같은 내용의 객체라도 다른 hash값으로 인해 서로 다른 버킷에 추가되게 되어 다른 객체로 인식하는 것입니다.

 

만약 hashCode() 오버라이딩을 통해 같은 내용의 객체가 같은 hash값이 나온다면 어떻게 될까요??

 

같은 hash값이면 같은 버킷 위치의 값이 나오기에 null조건에 맞지 않아 627줄의 else문으로 내려갑니다.

629줄의 if문을 만나 다시 한번 비교가 이루어지는데 여기서 equals()메서드가 등장합니다.

equals()오버라이딩을 했을 경우 같은 값의 객체는 true를 리턴하게 되어있기에 해당 조건문은 참이 되어 Node객체의 참조변수 ep의 값을 받습니다.

 참조변수 ep의 값을 받았기에 null이 아니며 onlyIfAbsentputVal4번째 매개변수로 false를 받았기에 650줄의 조건문은 참이 됩니다. 따라서 651번줄이 수행되고 기존 value값을 새로 들어온 value값으로 덮어씁니다. 이로써 HashSet, HashMap에서 새로운 객체를 추가 시 동일 객체일 경우 기존 값을 덮어쓰는 원리를 알 수 있습니다.

 

정리

equals()를 정의하지 않으면 해당 객체가 같은 객체인지 비교할 수 없고, 같은 객체라고 하더라도 hash값이 다르면 서로 다른 버킷에 할당될 수 있습니다. 그렇기 때문에 객체를 비교 시 equals()hashCode()를 같이 정의해줍니다.

 

참고사항

set 객체는 set에 저장되는 순간 들어오는 객체의 hashCode값을 기억하고 있습니다.

그러나, 나중에 name(hashCode에 사용되는 값)이 변경되어 hashCode값이 바뀌었을 때는 set 객체는 바뀌었다는 것을 인식하지 못합니다. 그렇기에 hashCode equals key Immutable을 사용할 수 없을 때는 변경 가능한 필드들은 hashCode equals에서 비교대상으로 사용하면 안 됩니다.

 

[참고사이트] 

참고사이트(1) 참고사이트(2) 참고사이트(3)