# **자바의 `equals()` 와 `hashCode()` 메서드 완벽 정리** ## **1. `equals()` 와 `hashCode()`란?** 자바에서 객체를 비교할 때, `==` 연산자는 **메모리 주소를 비교**한다. 하지만 **객체의 내용이 같은지를 비교**하려면 `equals()`를 재정의해야 한다. 또한, **HashMap, HashSet 같은 컬렉션에서 객체를 올바르게 저장하고 검색하려면 `hashCode()`도 재정의**해야 한다. --- ## **2. `equals()` 와 `hashCode()`의 기본 동작** ### ✅ **1. `equals()` 기본 구현 (`Object` 클래스)** 모든 클래스는 `Object` 클래스를 상속받으며, `Object`의 기본 `equals()`는 **메모리 주소를 비교**한다. ```java public boolean equals(Object obj) { return (this == obj); } ``` 즉, 기본적으로 `equals()`는 `==`과 동일한 동작을 한다. ### ✅ **2. `hashCode()` 기본 구현 (`Object` 클래스)** `Object`의 `hashCode()`는 **객체의 메모리 주소를 기반으로 해시 값을 반환**한다. ```java public native int hashCode(); ``` (※ `native`는 자바가 아닌 C/C++로 구현되었다는 의미) --- ## **3. `equals()`와 `hashCode()`를 함께 재정의해야 하는 이유** **예제:** `HashSet`에서 `equals()`만 재정의했을 때 발생하는 문제 ```java 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 set = new HashSet<>(); set.add(new Person("Alice")); System.out.println(set.contains(new Person("Alice"))); // false (올바르게 동작하지 않음!) } } ``` ✔ 이유: `HashSet`은 **`hashCode()`를 먼저 비교한 후 `equals()`를 실행**하는데, `hashCode()`가 재정의되지 않아서 `contains()`가 실패한다. ✅ 해결 방법: `hashCode()`를 `equals()`와 함께 재정의한다. --- ## **4. `equals()`와 `hashCode()` 올바르게 재정의하는 방법** ### ✅ **올바른 `equals()` 재정의** 1. **반사성** (`x.equals(x) == true`) 2. **대칭성** (`x.equals(y) == true` 면 `y.equals(x) == true`) 3. **추이성** (`x.equals(y) == true` & `y.equals(z) == true` 면 `x.equals(z) == true`) 4. **일관성** (`x.equals(y)`의 결과가 변하지 않음) 5. **null 비교 시 `false` 반환** (`x.equals(null) == false`) **예제 코드:** ```java @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()`를 가질 수는 있지만 가능하면 충돌을 줄이는 것이 좋다.** **예제 코드:** ```java @Override public int hashCode() { return Objects.hash(name); } ``` `Objects.hash()`는 `null` 체크까지 포함된 안전한 방식이다. --- ## **5. `equals()`와 `hashCode()`를 올바르게 구현한 예제** ```java 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 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부터 도입된 `record`는 `equals()`와 `hashCode()`를 자동 생성해준다. ```java record Person(String name) {} public class Main { public static void main(String[] args) { HashSet 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()`를 제대로 이해했으니, 컬렉션을 사용할 때 예상치 못한 버그를 피할 수 있을 것이다!