[Java] equals와 hashCode에 대한 고찰

김민수 / Minsu Kim·2024년 2월 4일
0

[Java]에 대한 고찰

목록 보기
1/7
post-thumbnail

⭐️ 이 주제를 정한 이유

처음 자바 프로그래밍을 시작했을 때, 테스트 코드를 작성하던 중 두 객체의 동등성을 비교하는데 어려움을 겪었다.
두 객체의 파라미터가 같아서 isEqualTo를 사용하여 두 객체를 비교했지만 예상치 못한 실패가 발생했다.
이에 대한 원인을 찾아보니 equals와 hashCode 메서드를 제대로 구현하지 않아서 발생한 문제였다.

그래서 이번에는 자바의 equals와 hashCode에 대해 깊이 알아보고자 한다.
이를 통해 객체의 동등성 비교와 해시 기반 컬렉션 사용에 대한 이해를 높이고, 효율적인 테스트 코드 작성에 도움이 될 것으로 기대한다.

⭐️ 객체 동등성(Object Equality)에 대해서

  • 물리적 동일성 (Physical Equality):
    • == 연산자를 사용하여 두 객체의 레퍼런스(주소)가 같은지 비교한다.
    • Object 클래스의 기본 equals 메서드는 이러한 물리적 동일성을 검사한다.
  • 논리적 동등성 (Logical Equality):
    • equals 메서드를 오버라이딩하여 개발자가 정의한 조건에 따라 두 객체가 논리적으로 동등한지를 판단한다.
    • 주로 객체의 내부 상태를 기반으로 동등성을 정의한다.

⭐️ equals 메서드란 무엇일까?

equals 메서드는 객체 간의 동등성을 비교하는데 사용한다.
Object 클래스에 정의된 equals 메서드는 다음과 같다.

public boolean equals(Object obj) {
    return (this == obj);
}

즉, 주소가 같은 자기 자신만이 같다고 인식한다.

String같은 경우는 equals도 같다고 인식하는데 그 이유는 오버라이드 되어 있기 때문이다.
예시는 다음과 같다.

String s1 = "John"; // new String("John") 과 동일
String s2 = "John";


System.out.println(s1 == s2); // 주소 비교 false

// String 클래스는 equals 메서드를 오버라이딩하여 값을 비교하도록 구현되어 있음
System.out.println(s1.equals(s2)); // 값 비교 true

그러면 같게 오버라이드할려면 어떻게 할까?

우선 User Class가 있다고 가정한다.

class User {
    private final Long id;
    private final String name;
    private final int age;

    public User(Long id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
}

여기서 id 값을 비교해서 객체가 동일하다고 판단하고 싶다.

그러면 다음과 같이 equals를 오버라이드하면 된다.

class User {
    //  - 생략 - 

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        User user = (User) o;
        return Objects.equals(id, user.id); // id 값으로 비교한다.
        // id와 name으로 비교하고 싶다면 다음과 같이 한다.
        // return Objects.equals(id, user.id) && Objects.equals(name, user.name);
    }
}

다음과 같이 name과 age가 달라도 id가 같을 경우 equals가 true가 된다.

@Test
void testEquals() {
    User user1 = new User(1L, "John", 20);
    User user2 = new User(1L, "Billy", 25);

    assertThat(user1).isEqualTo(user2); // 성공
}

⭐️ hashCode 메서드란 무엇일까?

일반적으로 IDE에서 equals를 오버라이드할 때, hashCode도 같이 오버라이드한다.

hashCode 메서드는 무엇이고, 왜 사용하는 것일까?

hashCode 메서드는 객체의 주소 값을 이용해서 해시 코드를 만든 후 반환한다.

hashCode는 해시 기반 자료 구조에서 객체를 빠르게 찾기 위해 사용한다.

예를 들어, HashSet, HashMap, HashTable과 같은 자료구조에서 객체를 검색하거나 저장할 때 hashCode를 사용하여 빠르게 해당 객체의 위치를 찾을 수 있다. 여기서 hashCode를 오버라이드 하지 않으면 다음과 같은 문제가 발생한다.

@Test
void testHashCode() {
    // HashSet 테스트
    User user1 = new User(1L, "John", 20);
    User user2 = new User(1L, "John", 20);

    Set<User> users = new HashSet<>();
    users.add(user1);
    users.add(user2);

    assertThat(users).hasSize(1); // size가 1이 아닌 2로 실패
}

equals를 오버라이드해서 동일한 id값을 가진 user1과 user2를 HashSet에 넣었을 때, size가 1인 것을 기대했으나, size가 2여서 테스트에 실패한다. 이것은 hashCode를 오버라이드하지 않아서 발생한다.

HashMap, HashSet, HashTable과 같은 자료구조는 hashCode와 equals가 모두 같을 경우에만 동일한 객체로 판단한다.

hashCode를 오버라이드하는 방법은 다음과 같다.

class User {
    // - 생략 -

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

⭐️ 요약

equals와 hashCode의 오버라이드를 통해서 논리적 동등성과 해시 기반 자료 구조에서의 효율적인 활용을 할 수 있다.

⭐️ 참고

https://www.baeldung.com/java-equals-hashcode-contracts

https://inpa.tistory.com/entry/JAVA-%E2%98%95-equals-hashCode-%EB%A9%94%EC%84%9C%EB%93%9C-%EA%B0%9C%EB%85%90-%ED%99%9C%EC%9A%A9-%ED%8C%8C%ED%97%A4%EC%B9%98%EA%B8%B0

https://mangkyu.tistory.com/101

profile
https://alstn113.tistory.com

0개의 댓글