Java

equals(), hashCode (), == 연산자

라우브 2021. 8. 25. 01:41

서론

우리가 임의의 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())

HashTable의 put메서드

위 메서드를 잘 보면 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를 재정의 해야함

'Java' 카테고리의 다른 글

스트림  (0) 2020.07.28
동작 파라미터화  (0) 2020.07.27