반응형
자바에서 객체의 동등성을 비교할 때 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)가 true면 y.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()는 name과 age가 같으면 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의 자동 생성 기능을 활용하면 안정적인 구현이 가능하다.
반응형
'Programming > Java' 카테고리의 다른 글
JAVA final, finally, finalize의 차이점 정리 (0) | 2025.04.15 |
---|---|
JAVA 불변 객체(Immutable Object)란 무엇이며, 어떻게 만들 수 있을까 (0) | 2025.04.15 |
JAVA ArrayList vs LinkedList 차이점과 선택 기준 (0) | 2025.04.10 |
JAVA HashMap vs Hashtable vs ConcurrentHashMap 차이점 (0) | 2025.04.10 |
JAVA 프로그램이 실행되는 흐름 (1) | 2025.04.09 |