BackEnd/Java

[Java] equals, hashCode 를 같이 구현하는 이유

샤아이인 2022. 5. 8.

 

이번글은 "Java에서 equals 와 hashCode를 왜 같이 구현할까?" 라는 의문에 대한 스스로의 답을 정리하는 글 입니다!

 

1. equals 만 재정의 할 경우

우선 설명에서 사용할 Item class를 살펴보자.

public class Item {

    private String name;

    public Item(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Item item = (Item) o;
        return Objects.equals(name, item.name);
    }
}

hashCode 없이 딱 equals만 오버라이딩 하고있다.

 

test코드를 통해 몇가지 확인해보자.

@Test
public void equals_test() {
    // given
    Item iPhone1 = new Item("iPhone");
    Item iPhone2 = new Item("iPhone");

    // when & then
    assertThat(iPhone1.equals(iPhone2)).isTrue();
}

equals를 오버라이딩 했기 때문에 필드값인 name이 같은 iPhone1 과 2는 같은 객채로 판별된다.

 

다음으로 List에 담아서 List의 size를 확인해보는 테스트 코드이다.

@Test
public void equals_list_size_test() {
    // given
    List<Item> items = new ArrayList<>();

    // when
    items.add(new Item("iPhone"));
    items.add(new Item("iPhone"));

    // then
    assertThat(items.size()).isEqualTo(2);
}

예측 가능하게 결과 사이즈는 2가 된다.

 

이번에는 Collection에 담되, 중복된 Item은 추가하지 않는다는 조건을 추가해보자.

적절한 Collection 으로는 Set이 떠오른다.

 

이를 활용한 테스트 코드는 다음과 같다.

@Test
public void equals_set_size_test() {
    // given
    Set<Item> items = new HashSet<>();

    // when
    items.add(new Item("iPhone"));
    items.add(new Item("iPhone"));

    // then
    assertThat(items.size()).isEqualTo(1); // 사이즈가 과연 1 일까?
}

추가된 두 Item 객체의 이름이 같아서 논리적으로 같은 객체라 판단하고 HashSet의 size가 1이 나올 거라 예상했다.

하지만 우리의 예상과 다르게 2가 출력되는것을 확인할 수 있다.

equals와 hashCode를 함께 Override 하지 않으면 예상과 다르게 작동하는 위와 같은 문제가 발생하게 된다.

정확히 말하면 hash 값을 사용하는 Collection(HashSet, HashMap, HashTable)을 사용할 때 문제가 발생한다.

 

2. 왜 그럴까?

hash값을 사용하는 Collection들은 객체가 같은지를 판단하기 위해 다음과 같은 과정을 거친다.

우선적으로 hashCode값을 통해 같은지 판단하고, 이후 equals 까지 같아야 같은 객체 로 판단되는 것 이다.

 

앞에서 봤었던 equals_set_size_test 에서 size가 2가 나온 이유도 이때문이다.

 

우리가 hashCode를 추가로 Override 하지 않았기 때문에 Object 클래스의 hashCode가 사용되었다.

Object 클래스의 hashCode 메서드는 객체의 고유한 주소 값을 int 값으로 변환하기 때문에 객체마다 다른 값을 리턴한다.

따라서 hashCode 반환값이 서로 달라 다른 객체로 인식했고, 2개의 item 모두 저장되어 size는 2가된 것 이다.

 

3. hashCode 재정의 하기

앞선 문제를 해결하기 위해 hashCode도 구현해주자!

public class Item {

    private String name;

    public Item(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Item item = (Item) o;
        return Objects.equals(name, item.name);
    }

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

Objects의 hash 메서드를 호출하는 로직으로 hashCode 메서드가 재정의 됐다.

Objects의 hash 메서드는 hashCode 메서드를 재정의하기 위해 간편히 사용할 수 있는 메서드이지만 속도가 느립니다...

 

이러한 구현은 모든 객체가 동일한 버킷에 저장되기 때문에 해시 테이블의 성능을 저하시킨다고 한다.

 

이게 무슨 소리일까?

 

둘 이상의 객체가 동일한 Bucket을 가리키면 단순히 Linked List에 저장됩니다. 다음과 같이 말이죠!

iPhone1, 2 같은 경우 동일한 Bucket을 가리키기 때문에 해당 Linked List에 뒤에 연속적으로 추가되어 저장되게 됩니다.

(위 경우 hash table은 Linked List 이며, 동일한 hashCode값을 가진 객체는 Bucket의 linked list에 추가되게 됩니다.)

 

이렇게 동일한 Bucket을 가리키는 item이 최악의 경우 N개라면, hashCode의 시간복잡도는 O(n)에 해당되게 됩니다.

 

성능에 아주 민감하지 않은 대부분의 경우 간편하게 Objects의 hash 메서드를 사용해서 hashCode 메서드를 재정의해도 문제 없다.

다만 성능에 민감한 경우, 직접 재정의해주는 게 좋다.

 

위와 같은 이유들로 equals와 hashCode는 항상 같이 재정의해주는 것 이 일반적이다.

 

4. 참고

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

 

Guide to hashCode() in Java | Baeldung

Learn how hashCode() works and how to implement it correctly.

www.baeldung.com

 

https://tecoble.techcourse.co.kr/post/2020-07-29-equals-and-hashCode/

 

equals와 hashCode는 왜 같이 재정의해야 할까?

equals와 hashCode는 같이 재정의하라는 말을 다들 한 번쯤 들어봤을 것이다. 대부분의 IDE Generate 기능에서도 equals와 hashCode를 같이 재정의해주며 lombok에서도 EqualsAndHashCode…

tecoble.techcourse.co.kr

 

'BackEnd > Java' 카테고리의 다른 글

[Java] 바이트코드 조작하기  (0) 2022.05.20
[Java] JVM 구조  (0) 2022.05.19
[Java] Java 에서의 Thread, Light Weight Process  (0) 2022.03.30
[Java] Exception 기초  (0) 2022.02.23
[Java] StringBuilder와 StringBuffer의 차이  (0) 2022.02.14

댓글