Java equals & hashCode ☕️

이수정·2023년 10월 27일
post-thumbnail

Java에서는 primitive type(기본 데이터 타입)이 같은지 비교할 때, == 연산자를 사용한다. 이 때문에 Java에서 문자열을 처음 비교하는 몇몇 사람들은 객체를 == 연산자로 비교하고 false를 반환받는다. 이 포스팅에서는 == 연산자부터 Object class의 equals & hashCode 까지의 내용을 다룬다.

== 연산자

== 연산자는 동일함을 뜻한다. 예로, a == b 라고 한다면 a는 b와 동일하다는 뜻이다.

그렇다면 어떤 기준으로 동일성을 판단할까?

이는 primitive type인지 reference type인지에 따라 다르다. Java의 메모리 영역은 static, stack, heap 영역으로 나뉜다. 이 중 stack 영역에 데이터에 대한 정보가 저장된다.

  • primitive type이라면, stack 영역에는 해당하는 변수의 실제 값이 저장된다. 때문에 == 연산자로 비교하면 실제 값이 비교되며 동일성을 판단하게 된다.
    int a = 1;
    int b = 1;
    int c = 2;
    
    System.out.println(a == b);
    System.out.println(a == c);
    
    // 결과
    true
    false
  • 하지만 reference type이라면, heap 영역에 객체에 대한 정보를 담고, 데이터를 담은 heap 영역의 참조 값을 stack 영역에 담게 된다. 객체를 == 연산자로 비교하게 된다면 실제 데이터값이 아닌 참조 값을 비교하게 되는 것이다.
    class Money {
        long amount;
    
        Money(long amount) {
            this.amount = amount;
        }
    }
    Money a = new Money(1000);
    Money b = new Money(1000);
    Money c = new Money(5000);
    
    System.out.println(a == b);
    System.out.println(a == c);
    
    // 결과
    false
    false
    • 위와 같이 두 Money 객체가 서로 다른 주소를 갖고 있어도 “같은 1000원이라면 같다”라는 결과를 얻고 싶을 수 있다. 이때, Object class의 equals 를 이용할 수 있다.

equals 메서드

아래 코드는 Object class의 equals 메서드의 코드이다.

/**
 * Indicates whether some other object is "equal to" this one.
 * <p>
 * The {@code equals} method implements an equivalence relation
 * on non-null object references:
 * ...생략
**/
public boolean equals(Object obj) {
    return (this == obj);
}

equals() 메서드의 comment를 보면 “equivalence relation을 구현한다”라고 적혀있다. 즉, equals는 동등성을 구현한다는 것이고, 우리는 이 메서드를 재정의하여 비교 대상이 논리적으로 같은 데이터 값을 가졌는지를 비교할 수 있다는 것이다. equals는 아래와 같이 재정의해 볼 수 있다.

class Money {
    long amount;

    Money(long amount) {
        this.amount = amount;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Money money = (Money) o;
        return amount == money.amount;
    }

    @Override
    public int hashCode() {
        return Objects.hash(amount);
    }
}

그런데 구현된 코드에 다루지 않은 내용인 hashCode()가 재정의 되어 있다. 사실 Object class의 equals 메서드의 comment에는 아래와 같이 자바 규칙에 대한 내용이 포함되어 있다.

Note that it is generally necessary to override the hashCode method whenever this method is overridden, so as to maintain the general contract for the hashCode method, which states that equal objects must have equal hash codes.

즉, 일반적으로 equals 메서드를 재정의하면 hashCode 메서드도 재정의해야 한다는 것이다. 더 자세히 알아보자.

hashCode 메서드

먼저, hash라는 단어부터 알아봐야 한다. 간단히 말하자면,

  1. hash는 입력된 데이터를 고정된 길이의 데이터로 반환하는 값을 뜻한다.
  2. 이때 고정된 길이로 바꾸는 함수를 hash function이라 하고,
  3. hash function에서 hash를 반환하여 hash table에 저장하는 과정을 hashing이라고 한다.
  4. hash table은 키와 값을 저장하는 데이터 구조를 말한다.

hash는 저장, 읽기 작업에 사용되고, 검색 속도가 빠르다는 장점이 있다.

(주의 🙏🏻) hashCode ≠ 주소값 → hashCode는 주소값이 아니다!

/**
     * Returns a hash code value for the object. This method is
     * supported for the benefit of hash tables such as those provided by
     * {@link java.util.HashMap}.
     * <p>
     * The general contract of {@code hashCode} is:
     * <ul>
     * <li>Whenever it is invoked on the same object more than once during
     *     an execution of a Java application, the {@code hashCode} method
     *     must consistently return the same integer, provided no information
     *     used in {@code equals} comparisons on the object is modified.
     *     This integer need not remain consistent from one execution of an
     *     application to another execution of the same application.
     * <li>If two objects are equal according to the {@code equals(Object)}
     *     method, then calling the {@code hashCode} method on each of
     *     the two objects must produce the same integer result.
     * <li>It is <em>not</em> required that if two objects are unequal
     *     according to the {@link java.lang.Object#equals(java.lang.Object)}
     *     method, then calling the {@code hashCode} method on each of the
     *     two objects must produce distinct integer results.  However, the
     *     programmer should be aware that producing distinct integer results
     *     for unequal objects may improve the performance of hash tables.
     * </ul>
     * <p>
     * As much as is reasonably practical, the hashCode method defined
     * by class {@code Object} does return distinct integers for
     * distinct objects. (The hashCode may or may not be implemented
     * as some function of an object's memory address at some point
     * in time.)
     *
     * @return  a hash code value for this object.
     * @see     java.lang.Object#equals(java.lang.Object)
     * @see     java.lang.System#identityHashCode
     */
    @HotSpotIntrinsicCandidate
    public native int hashCode();

Object class의 hashCode()의 코드를 보면, 중요 내용을 아래와 같이 정리할 수 있다.

  1. 객체에 대한 해시 코드를 반환한다. 이 메서드는 해시 맵과 같이 해시 테이블의 이점을 위해 지원된다.
  2. 객체 비교에 사용되는 정보가 수정되지 않는다면 같은 해시 코드를 일관성 있게 반환해야 한다.
  3. 동등하지 않은 객체에 대해 서로 다른 해시 코드를 생성하면 해시 테이블의 성능을 향상시킨다.

결론

equals의 내용과 hashCode의 내용을 합해 equals와 hashCode의 관계를 아래와 같이 정리해 보았다.

  1. if o1.equals(o2) 면, o1.hashCode() == o2.hashCode() 가 성립해야 한다.
  2. if o1.hashCode() == o2.hashCode() 면, o1.equals(o2) 가 성립될 필요는 없다.
  3. hashCode는 검색에 큰 도움을 주고, equals 메서드의 비교 대상을 기준으로 hashCode를 일관성있게 반환해야 한다.
⚠️ 잘못된 정보가 있다면, 댓글 부탁 드립니다. 감사합니다. 😀

정리 ✨

  1. == 연산자는 동일성, equals 메서드는 동등성을 다룬다.
  2. hashCode ≠ 주소값이고 비교 내용이 같다면 같은 hashCode를 반환해야 한다.

참고

Oracle Java docs : Object

profile
쌓다 보면 탑이 될 거야 🗼

0개의 댓글