Notice
Recent Posts
Recent Comments
Link
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Archives
Today
Total
관리 메뉴

밤빵's 개발일지

[TIL]20241206 equals()와 hashCode()를 재정의 해야하는 이유? 본문

개발Article

[TIL]20241206 equals()와 hashCode()를 재정의 해야하는 이유?

최밤빵 2024. 12. 6. 01:17

equals()hashCode() 이 두 메서드는 객체의 동등성을 비교하고, 해시 기반 컬렉션에서 올바르게 동작하기 위해 중요한 역할을 한다. 이번 개발일지에서는 equals()hashCode()의 기본 개념, 그리고 왜 재정의해야 하는지와 그 결과로 얻는 이점에 대해 학습한 내용을 정리했다. 

 

equals()와 hashCode()란?

  • equals(): 두 객체가 논리적으로 같은지를 비교하는 메서드이다. 기본적으로 Object 클래스에 정의되어 있고, 동일한 객체 참조를 가리키는지(메모리 주소가 같은지) 비교한다. 하지만 대부분의 경우 객체의 속성 값을 기준으로 논리적인 동등성을 판단해야 하기때문에 재정의가 필요하다.
  • hashCode(): 객체를 해시 테이블과 같은 자료구조에 사용할 때 객체를 고유하게 식별할 수 있는 정수 값을 반환하는 메서드이다. Object 클래스에 기본 구현이 있지만, 우리가 정의하는 클래스에서 동등성을 일관되게 유지하려면 이 메서드도 재정의해야 한다.

왜 equals()와 hashCode()를 재정의해야 할까?

  1. 컬렉션의 동작 보장: equals()hashCode()를 재정의하는 주요 이유 중 하나는 해시 기반 컬렉션(HashSet, HashMap 등)이 제대로 동작하도록 보장하기 위해서이다. 해시 기반 컬렉션은 객체를 저장하거나 검색할 때 hashCode()를 사용하여 객체의 해시값을 비교하고, equals()를 사용하여 객체의 동등성을 확인한다. 만약 이 두 메서드의 규칙을 어기면, 해시 컬렉션에서 같은 객체임에도 불구하고 중복 삽입이 되거나 검색에 실패할 수 있다.
    List<Product> productList = new ArrayList<>();
    productList.add(new Product("Laptop", 1000));
    productList.add(new Product("Laptop", 1000));
    
    Set<Product> productSet = new HashSet<>(productList);
    
    if (productList.size() != productSet.size()) {
        throw new IllegalArgumentException("중복된 객체가 있습니다.");
    }
    → productList에는 동일한 속성을 가진 Product 객체가 두 개 추가되었다. productSet으로 변환해서 중복 객체를 제거하려고 했지만, equals()hashCode() 메서드를 재정의하지 않았다면 두 객체는 서로 다른 것으로 인식되어 중복 제거가 되지 않으며, 예외가 발생하지 않고 다음 코드로 넘어간다. 기본 hashCode()equals()가 메모리 주소를 기준으로 비교하기 때문이다.
  2. 동등성의 일관성: equals() 메서드를 재정의할 때 반드시 hashCode()도 함께 재정의해야 한다. 그 이유는 두 객체가 equals()에 의해 같다고 판단되면, 이 두 객체의 hashCode() 값도 같아야 하기 때문이다. 이 규칙을 지키지 않으면, 해시 기반 컬렉션에서 예상치 못한 동작이 발생할 수 있다. 두 객체가 equals()에 의해 같다고 판단되지만 hashCode()가 다르면, 해시 컬렉션에서는 이 두 객체를 서로 다른 객체로 취급할 수 있다.
  3. 객체의 고유 식별: 사용자 정의 객체가 논리적으로 같다고 판단되는 경우, 효율적으로 비교하고 처리하기 위해 equals()hashCode()를 재정의해야 한다. 기본 구현은 객체의 메모리 주소를 기반으로 비교하기 때문에, 객체의 속성 값이 같더라도 서로 다른 객체로 인식될 수 있다. 따라서 객체의 속성 값을 기반으로 비교하기 위해서는 equals()를 재정의해야 하고, 해시 기반 컬렉션에서 이를 올바르게 처리하기 위해 hashCode()도 일관되게 재정의해야 한다.

재정의 예제

public class Product {
    private String name;
    private int price;

    public Product(String name, int price) {
        this.name = name;
        this.price = price;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Product product = (Product) obj;
        return price == product.price && Objects.equals(name, product.name);
    }

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

nameprice 속성을 기반으로 객체의 동등성을 판단한다. equals()에서 두 객체의 속성 값을 비교하고, hashCode()에서는 이 속성을 기반으로 해시 값을 생성한다. HashMap이나 HashSet과 같은 컬렉션에서도 일관된 동작을 보장할 수 있다.


equals()와 hashCode() 재정의 시 주의사항

  • 두 객체가 같다면, hashCode() 값도 반드시 같아야 한다.
  • hashCode() 값이 같다고 해서, equals()가 반드시 true를 반환할 필요는 없다. 해시 충돌이 발생할 수 있기 때문이다.
  • hashCode()는 가능한 서로 다른 객체에 대해 다른 값을 반환하도록 구현해야 한다. 해시 충돌이 적을수록 해시 기반 컬렉션의 성능이 향상된다.
  • 일관성(consistency): equals()hashCode()는 객체의 상태가 변경되지 않는 한 항상 동일한 값을 반환해야 한다. 만약 객체의 필드가 변경되었을 때, equals()hashCode()의 결과가 달라질 경우 컬렉션에서의 예상치 못한 동작이 발생할 수 있다.

재정의 시 유용한 도구와 패턴

  • Objects.equals()Objects.hash() 사용: equals()hashCode()를 구현할 때 Objects 클래스의 유틸리티 메서드를 사용하는 것이 좋다. 코드의 가독성을 높이고, null 처리를 간단하게 해준다.
  • IDE 자동 생성 기능 활용: IntelliJ IDEA에서는 equals()hashCode() 메서드를 자동으로 생성해주는 기능을 제공하기 때문에, 규칙에 맞게 메서드를 쉽게 재정의할 수 있다.

equals()와 hashCode()를 재정의하는 것은 Java에서 중요한 작업이다. 이 두 메서드를 올바르게 재정의하지 않으면, 해시 기반 컬렉션에서 비정상적인 동작이 발생할 수 있고, 객체의 논리적 동등성을 보장하지 못하게 된다. 객체의 동등성을 고려해야 할 때 이 두 메서드를 올바르게 재정의하는 것이 필수적이다.