163 lines
5.2 KiB
Markdown
163 lines
5.2 KiB
Markdown
# **자바의 `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<Person> 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<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부터 도입된 `record`는 `equals()`와 `hashCode()`를 자동 생성해준다.
|
|
|
|
```java
|
|
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()`를 제대로 이해했으니, 컬렉션을 사용할 때 예상치 못한 버그를 피할 수 있을 것이다! |