Files
java-examples/docs/EqualsAndHashCode.md

5.2 KiB

자바의 equals()hashCode() 메서드 완벽 정리

1. equals()hashCode()란?

자바에서 객체를 비교할 때, == 연산자는 메모리 주소를 비교한다.
하지만 객체의 내용이 같은지를 비교하려면 equals()를 재정의해야 한다.

또한, HashMap, HashSet 같은 컬렉션에서 객체를 올바르게 저장하고 검색하려면 hashCode()도 재정의해야 한다.


2. equals()hashCode()의 기본 동작

1. equals() 기본 구현 (Object 클래스)

모든 클래스는 Object 클래스를 상속받으며, Object의 기본 equals()메모리 주소를 비교한다.

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

즉, 기본적으로 equals()==과 동일한 동작을 한다.

2. hashCode() 기본 구현 (Object 클래스)

ObjecthashCode()객체의 메모리 주소를 기반으로 해시 값을 반환한다.

public native int hashCode();

(※ native는 자바가 아닌 C/C++로 구현되었다는 의미)


3. equals()hashCode()를 함께 재정의해야 하는 이유

예제: HashSet에서 equals()만 재정의했을 때 발생하는 문제

import java.util.HashSet;
import java.util.Objects;

class Person {
    String name;

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

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

public class Main {
    public static void main(String[] args) {
        HashSet<Person> set = new HashSet<>();
        set.add(new Person("Alice"));
        System.out.println(set.contains(new Person("Alice"))); // false (올바르게 동작하지 않음!)
    }
}

✔ 이유: HashSethashCode()를 먼저 비교한 후 equals()를 실행하는데, hashCode()가 재정의되지 않아서 contains()가 실패한다.

해결 방법: hashCode()equals()와 함께 재정의한다.


4. equals()hashCode() 올바르게 재정의하는 방법

올바른 equals() 재정의

  1. 반사성 (x.equals(x) == true)
  2. 대칭성 (x.equals(y) == truey.equals(x) == true)
  3. 추이성 (x.equals(y) == true & y.equals(z) == truex.equals(z) == true)
  4. 일관성 (x.equals(y)의 결과가 변하지 않음)
  5. null 비교 시 false 반환 (x.equals(null) == false)

예제 코드:

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

올바른 hashCode() 재정의

같은 객체는 같은 hashCode()를 가져야 한다.
다른 객체라도 같은 hashCode()를 가질 수는 있지만 가능하면 충돌을 줄이는 것이 좋다.

예제 코드:

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

Objects.hash()null 체크까지 포함된 안전한 방식이다.


5. equals()hashCode()를 올바르게 구현한 예제

import java.util.HashSet;
import java.util.Objects;

class Person {
    String name;

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

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

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

public class Main {
    public static void main(String[] args) {
        HashSet<Person> set = new HashSet<>();
        set.add(new Person("Alice"));
        System.out.println(set.contains(new Person("Alice"))); // true (올바르게 동작!)
    }
}

equals()hashCode()를 함께 재정의했기 때문에 contains()가 정상적으로 동작한다.


6. record를 사용하면 자동 구현됨! (Java 14+)

자바 14부터 도입된 recordequals()hashCode()를 자동 생성해준다.

record Person(String name) {}

public class Main {
    public static void main(String[] args) {
        HashSet<Person> set = new HashSet<>();
        set.add(new Person("Alice"));
        System.out.println(set.contains(new Person("Alice"))); // true
    }
}

record자동으로 equals()hashCode()를 올바르게 구현해준다.


7. 결론

  • equals()만 재정의하면 HashSet, HashMap 등의 컬렉션에서 문제가 발생할 수 있다.
  • hashCode()도 함께 재정의해야 컬렉션에서 정상 동작한다.
  • record를 사용하면 자동으로 equals()hashCode()가 구현된다.

이제 equals()hashCode()를 제대로 이해했으니, 컬렉션을 사용할 때 예상치 못한 버그를 피할 수 있을 것이다!