본문 바로가기

Programming/Java

JAVA equals()와 hashCode() 오버라이딩할 때 주의할 점

반응형

자바에서 객체의 동등성을 비교할 때 equals()hashCode() 메서드를 올바르게 구현하는 것은 매우 중요하다. 특히, 컬렉션 프레임워크에서 HashMap, HashSet 등을 사용할 때 올바른 동작을 보장하려면 반드시 이 두 메서드를 함께 오버라이딩해야 한다. 이번 포스팅에서는 equals()hashCode()를 오버라이딩할 때 주의해야 할 점을 정리해보겠다.

1. equals()와 hashCode()의 기본 개념

equals()란?

  • equals()는 두 객체가 논리적으로 동등한지를 비교하는 메서드이다.
  • 기본적으로 Object 클래스에서 제공하는 equals()== 연산자와 동일하게 동작하여 객체의 참조(주소) 비교를 수행한다.
  • 필요에 따라 오버라이딩하여 객체의 특정 필드 값을 기준으로 동등성을 비교할 수 있다.

기본 equals() 구현 예제

class Person {
    private String name;
    
    public Person(String name) {
        this.name = name;
    }
}

Person p1 = new Person("Alice");
Person p2 = new Person("Alice");
System.out.println(p1.equals(p2)); // false (기본 equals()는 참조 비교를 수행)

hashCode()란?

  • hashCode()는 객체를 해시 기반 컬렉션(HashMap, HashSet 등)에서 사용할 때 해시 값을 반환하는 메서드이다.
  • hashCode()가 올바르게 구현되지 않으면 같은 객체임에도 다른 해시 값을 반환하여 컬렉션에서 예기치 않은 동작이 발생할 수 있다.

 

2. equals()와 hashCode() 오버라이딩 규칙

▶ equals()를 오버라이딩하면 hashCode()도 반드시 오버라이딩해야 한다.

  • 같은 객체라면 같은 해시 값을 가져야 한다는 원칙을 지켜야 한다.
  • 그렇지 않으면 HashMap이나 HashSet에서 객체가 정상적으로 저장되지 않을 수 있다.

 equals() 구현 시 반사성, 대칭성, 추이성을 보장해야 한다.

  • 반사성(Reflexive): x.equals(x)는 항상 true여야 한다.
  • 대칭성(Symmetric): x.equals(y)truey.equals(x)true여야 한다.
  • 추이성(Transitive): x.equals(y)true이고 y.equals(z)true라면 x.equals(z)true여야 한다.

hashCode()는 같은 객체에 대해 항상 같은 값을 반환해야 한다.

  • 같은 객체(equals()true를 반환하는 경우)는 항상 같은 해시 코드를 반환해야 한다.
  • 하지만, 다른 객체는 같은 해시 값을 반환할 수도 있다. (충돌 가능)

 

3. equals()와 hashCode() 올바른 구현 예제

import java.util.Objects;

class Person {
    private String name;
    private int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    @Override
    public boolean equals(Object obj) {
        // 동일한 객체인지 참조 비교
        if (this == obj) return true;
        
        // 객체가 null이거나 클래스가 다르면 false 반환
        if (obj == null || getClass() != obj.getClass()) return false;
        
        // 실제 필드 값 비교
        Person person = (Person) obj;
        return age == person.age && name.equals(person.name);
    }
    
    @Override
    public int hashCode() {
        // name과 age 필드를 기반으로 해시 코드 생성
        return Objects.hash(name, age);
    }
    
    public static void main(String[] args) {
        Person p1 = new Person("Alice", 25);
        Person p2 = new Person("Alice", 25);
        Person p3 = new Person("Bob", 30);

        System.out.println(p1.equals(p2)); // true (같은 값이므로 동등함)
        System.out.println(p1.equals(p3)); // false (값이 다르므로 동등하지 않음)
        
        System.out.println(p1.hashCode()); // p1과 p2는 같은 해시 코드 반환
        System.out.println(p2.hashCode()); // p1과 p2는 같은 해시 코드 반환
        System.out.println(p3.hashCode()); // p3는 다른 해시 코드 반환
    }
}

설명

  • equals()nameage가 같으면 true를 반환하도록 구현했다.
  • hashCode()Objects.hash(name, age)를 사용해 일관된 해시 값을 반환하도록 했다.
  • 따라서 같은 값의 객체는 같은 해시 코드를 가지며, HashSet과 같은 자료구조에서도 정상적으로 동작한다.

 

4. equals()와 hashCode()를 잘못 구현하면 발생하는 문제

hashCode()를 오버라이딩하지 않으면?

Person p1 = new Person("Alice", 25);
Person p2 = new Person("Alice", 25);
Set<Person> set = new HashSet<>();
set.add(p1);
set.add(p2);
System.out.println(set.size()); // 예상: 1, 실제: 2 (hashCode() 미구현 시 문제 발생)
  • HashSet은 내부적으로 hashCode()를 사용하여 중복을 확인하지만, hashCode()를 오버라이딩하지 않으면 서로 다른 해시 값이 생성될 수 있다.

equals()를 잘못 구현하면?

class BadPerson {
    private String name;
    
    public BadPerson(String name) {
        this.name = name;
    }
    
    @Override
    public boolean equals(Object obj) {
        return true; // 잘못된 구현
    }
}

BadPerson p1 = new BadPerson("Alice");
BadPerson p2 = new BadPerson("Bob");
System.out.println(p1.equals(p2)); // 항상 true (잘못된 동작)
  • equals()를 잘못 구현하면 모든 객체가 같다고 인식되어 논리적 오류가 발생한다.

 

5. 결론

  • equals()hashCode()는 항상 함께 오버라이딩해야 한다.
  • equals()는 반사성, 대칭성, 추이성을 지켜야 한다.
  • hashCode()는 같은 객체에 대해 항상 동일한 값을 반환해야 한다.
  • Objects.hash() 또는 IDE의 자동 생성 기능을 활용하면 안정적인 구현이 가능하다.
반응형