상세 컨텐츠

본문 제목

[java] hashcode()와 equals() 메서드는 언제 사용하고 왜 사용할까?

프로그래밍/기타

by jisooo 2020. 7. 1. 14:01

본문

객체 비교(equals()와 ==)

// Object의 기본 equals 메서드
public boolean equals(Object obj) {
    return (this == obj);
}

Object 클래스의 equals()는 위와 같은 기능으로써 이 메소드는 비교 연사자인 == 과 동일한 결과를 리턴한다. 오로지 참조값(객체의 주소값)이 같은지, 다시말하면 동일 객체인지를 확인하는 기능이다.

자바에서는 두 객체를 동등 비교할 때 equals() 메소드를 흔히 사용한다. equals() 메소드는 두 객체를 비교해서 논리적으로 동등하면 true를 리턴하고 그렇지 않으면 false를 리턴한다.

논리적으로 동등하다는것은 둘의 참조값이 다르더라도 객체 내부 value는 같다는것을 의미한다. 이 equals함수를 재정의한 대표적인 예가 String class이다. String class는 equals() 메소드를 재정의해서 번지비교가 아닌 문자열 '값'을 비교한다.

  • 동일성 비교는 == 비교다. 객체 인스턴스의 주소 값을 비교한다.

    • primitive data type의 경우 ==를 통해 값 비교가 가능하다.
  • 동등성 비교는 equals() 메소드를 사용해서 객체 내부의 값을 비교한다.

 

* 참고로 primitive 타입이 == 비교를 통해 값 비교가 가능한 이유는 아래와 같다.

변수 선언부는 Java Runtime Data Area의 Stack 영역에 저장이 되고, 해당 변수에 저장된 상수는 Runtime Constant Pool에 저장되어있다. Stack의 변수 선언부는 해당 Runtime Contant Pool의 주소값을 가지게 되고, 만약 다른 변수도 같은 상수를 저장하고 있다면, 이 다른 변수도 같은 Runtime Contant Pool의 주소값을 가지게 때문에 엄밀히 말하면 primitive type 역시 주소값 비교가 되는 것이다.

 

 

Java hash code란

객체 해시코드란 객체를 식별하는 하나의 정수값을 말한다. Object의 hashCode() 메소드는 객체의 메모리 번지를 이용해서 해시코드를 만들어 리턴하기 때문에 객체 마다 다른 값을 가지고 있다. 객체의 값을 동등성 비교시 hashCode()를 오버라이딩할 필요성이 있는데, 컬렉션 프레임워크에서 HashSet, HashMap, HashTable은 다음과 같은 방법으로 두 객체가 동등한지 비교한다.

우선

hashCode()

메소드를 실행해서 리턴된 해시코드 값이 같은지를 본다. 해시 코드값이 다르면 다른 객체로 판단하고, 해시 코드값이 같으면

equals()

메소드로 다시 비교한다. 이 두 개가 모두 맞아야 동등 객체로 판단한다. 즉, 해시코드 값이 다른 엔트리끼리는 동치성 비교를 시도조차 하지 않는다.

Java HashTable

equals와 hashcode 메서드를 이해하기 위해서 자바에서 HashTable이 작동하는 원리를 간단히 살펴보자.

(참고로 HashTable 뿐만 아니라 HashMap, HashSet 모두 아래의 동작 원리를 동일하다.)

 

HashTable은 <key,value> 형태로 데이터를 저장한다. 이 때 해시 함수(Hash Function)을 이용하여 key값을 기준으로 고유한 식별값인 해시값을 만든다. (hashcode가 해시값을 만드는 역할을 한다.) 이 해시값을 버킷(Bucket)에 저장한다.

하지만 HashTable 크기는 한정적이기 때문에 같은 서로 다른 객체라 하더라도 같은 해시값을 갖게 될 수도 있다.

이것을 **해시 충돌(Hash Collisions)**이라고 한다.

이런 경우 아래와 같이 해당 버킷(Bucket)에 LinkedList 형태로 객체를 추가한다.

(* 참고로 java8인가 9버전부터 LinkedList 아이템의 갯수가 8개 이상으로 넘어가면 TreeMap 자료구조로 저장된다고 한다.)

 

이처럼 같은 해시값의 버킷 안에 다른 객체가 있는 경우 equals 메서드가 사용된다.

 

HashTable에 put 메서드로 객체를 추가하는 경우

  • 값이 같은 객체가 이미 있다면(equals()가 true) 기존 객체를 덮어쓴다.
  • 값이 같은 객체가 없다면(equals()가 false) 해당 entry를 LinkedList에 추가한다.

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

  • 값이 같은 객체가 있다면 (equals()가 true) 그 객체를 리턴한다.
  • 값이 같은 객체가 없다면(equals()가 false) null을 리턴한다.

위 그림에서 세 객체 (Entry<K1,V1>, Entry<K2,V2>, Entry<K3,V3>)는 서로 같은 해시값을 같는다. 따라서 hashcode() 메서드는 같은 값을 리턴한다. 하지만 서로 값이 다른 객체이기 때문에 equals() 메서드는 false를 리턴한다.

 

equals()와 hashcode()를 같이 재정의해야 하는 이유

만약 equals()와 hashcode() 중 하나만 재정의 하면 어떻게 될까? 위 예에서도 봤듯이 hashcode()를 재정의 하지 않으면 같은 값 객체라도 해시값이 다를 수 있다. 따라서 HashTable에서 해당 객체가 저장된 버킷을 찾을 수 없다.

반대로 equals()를 재정의하지 않으면 hashcode()가 만든 해시값을 이용해 객체가 저장된 버킷을 찾을 수는 있지만 해당 객체가 자신과 같은 객체인지 값을 비교할 수 없기 때문에 null을 리턴하게 된다. 따라서 역시 원하는 객체를 찾을 수 없다.

이러한 이유로 객체의 정확한 동등 비교를 위해서는 (특히 Hash 관련 컬렉션 프레임워크를 사용할때!) Object의 equals() 메소드만 재정의하지 말고 hashCode()메소드도 재정의해서 논리적 동등 객체일경우 동일한 해시코드가 리턴되도록 해야한다.

관련글 더보기

댓글 영역