목차
🔍 자바 객체 비교의 핵심! equals()와 hashCode() 완벽 이해와 구현 방법
"왜 HashMap에서 내가 만든 객체가 제대로 비교되지 않을까?"
"같은 값을 가졌는데 Set에 중복으로 들어가는 이유는 뭐지?"
👉 그 해답은 바로 equals()와 hashCode() 메서드에 있습니다.
이 두 메서드를 정확히 이해하고 구현하지 않으면, 버그 없이 자바 컬렉션을 쓰는 건 불가능에 가깝습니다.
이 글에서 equals()와 hashCode()의 개념부터 실무 적용까지 완벽하게 정리해드릴게요.
1. equals()와 hashCode()란? 주요 개념과 특징
자바에서 equals()와 hashCode()는 Object 클래스에 정의된 메서드로, 객체의 동등성과 해시 기반 데이터 구조에서 중요한 역할을 합니다.
✅ equals()
- 역할: 두 객체가 논리적으로 같은지 비교합니다.
- 기본 구현: Object 클래스의 기본 equals()는 참조 동등성(==)만 확인합니다.
- 특징: 개발자가 오버라이드하여 객체의 필드 값을 기반으로 동등성을 정의할 수 있습니다.
✅ hashCode()
- 역할: 객체의 해시 값을 반환하며, HashMap이나 HashSet 같은 해시 기반 컬렉션에서 사용됩니다.
- 기본 구현: 객체의 메모리 주소를 기반으로 한 고유 값을 반환합니다.
- 특징: equals()로 같은 객체는 반드시 동일한 hashCode() 값을 가져야 합니다(계약 규칙).
✅ 규칙
- equals()로 같으면 hashCode()도 같아야 함.
- hashCode()가 다르면 equals()도 달라야 함(반대는 성립하지 않음).
- 객체 상태가 변하지 않는 한 hashCode()는 항상 동일한 값을 반환해야 함.
✔ equals()의 규칙
- 반사성: a.equals(a)는 항상 true
- 대칭성: a.equals(b)가 true면 b.equals(a)도 true
- 추이성: a.equals(b) && b.equals(c)이면 a.equals(c)도 true
- 일관성: 비교 결과는 변하지 않아야 함 (불변 객체일 경우)
- null 비교: a.equals(null)은 항상 false
✔ hashCode()의 규칙
- 같은 객체는 항상 같은 hashCode()를 반환해야 함
- 동일한 hashCode()를 가진다고 해서 반드시 equals()가 true는 아님
- equals()가 true면, 반드시 hashCode()도 같아야 함
쉽게 말해, equals()는 "내용이 같은지" 확인하고, hashCode()는 "빠른 검색을 위한 고유 번호"를 제공한다고 생각하면 됩니다.
2. equals()와 hashCode() 구현 예시 및 코드 설명
예제 1: 사용자(User) 클래스
import java.util.Objects;
public class User {
private String id;
private String name;
public User(String id, String name) {
this.id = id;
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true; // 동일 객체
if (o == null || getClass() != o.getClass()) return false; // 클래스 타입 확인
User user = (User) o;
return Objects.equals(id, user.id); // ID 기준으로 비교
}
@Override
public int hashCode() {
return Objects.hash(id); // ID 기반으로 해시 생성
}
}
- equals()에서는 클래스 타입 체크 후, 중요한 필드인 id만을 기준으로 비교합니다.
- hashCode()도 id 필드를 기준으로 해시코드를 생성합니다.
- Objects.equals()와 Objects.hash()를 사용하면 NPE 방지와 가독성 향상 효과가 있습니다.
예제 2: Person 클래스에서 구현
public 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; // 동일 참조 확인
if (obj == null || getClass() != obj.getClass()) return false; // null 또는 클래스 다름
Person other = (Person) obj; // 타입 캐스팅
return age == other.age && name.equals(other.name); // 필드 비교
}
@Override
public int hashCode() {
int result = name.hashCode(); // name의 해시값
result = 31 * result + age; // age를 결합 (31은 소수로 충돌 최소화)
return result;
}
public static void main(String[] args) {
Person p1 = new Person("홍길동", 30);
Person p2 = new Person("홍길동", 30);
System.out.println("equals: " + p1.equals(p2)); // 출력: true
System.out.println("hashCode: " + (p1.hashCode() == p2.hashCode())); // 출력: true
}
}
equals():
- this == obj: 동일한 참조면 바로 true.
- null 체크와 클래스 확인으로 안전성 확보.
- 필드(name, age)를 비교해 논리적 동등성 판단.
hashCode():
- name.hashCode()로 문자열의 해시 값을 가져옴.
- 소수 31을 곱해 충돌 가능성을 줄이고, age를 추가해 고유성 강화.
3. 실무에서 equals()와 hashCode() 활용 사례
실무 예시 1: HashSet에서 중복 제거
import java.util.HashSet;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Set<User> users = new HashSet<>();
users.add(new User("001", "Alice"));
users.add(new User("001", "Alice")); // 같은 id, equals() 같음
System.out.println("유저 수: " + users.size()); // 1 출력
}
}
- equals()와 hashCode()를 올바르게 구현하지 않으면 위 예제에서 객체가 2개로 취급됩니다.
- 실무에서는 회원 중복 등록, 권한 체크, 로그인 처리 등에서 이 문제가 자주 발생합니다.
실무 예시 2: HashMap에서 키로 사용
import java.util.HashMap;
public class User {
private int id;
private String username;
public User(int id, String username) {
this.id = id;
this.username = username;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
User other = (User) obj;
return id == other.id && username.equals(other.username);
}
@Override
public int hashCode() {
return 31 * id + username.hashCode();
}
public static void main(String[] args) {
HashMap<User, String> userMap = new HashMap<>();
User user1 = new User(1, "admin");
User user2 = new User(1, "admin");
userMap.put(user1, "관리자");
System.out.println(userMap.get(user2)); // 출력: 관리자
}
}
- 활용 이유: equals()와 hashCode()가 제대로 구현되지 않으면 user1과 user2를 다른 키로 인식해 데이터가 중복되거나 조회되지 않을 수 있습니다.
4. equals()와 hashCode()의 장단점: 주의사항
✅ 장점
- 컬렉션 (HashMap, HashSet, Hashtable) 에서의 정확한 비교 가능
- 비즈니스 로직에 맞춘 의미 있는 비교 구현 가능
- 성능 향상 (효율적인 해시 분포)
❌ 단점 및 주의사항
- equals()와 hashCode()의 불일치로 인해 버그 발생 가능성 높음
- hashCode()를 과도하게 복잡하게 구현하면 해시 성능 저하
- 모든 필드를 비교하면 성능 하락, 핵심 필드만 사용하는 것이 좋음
- 구현 시 IDE 자동 생성 기능 사용을 추천 (Alt + Insert in IntelliJ)
✅ 결론: equals()와 hashCode()는 객체 비교의 필수 요소!
자바에서 객체를 비교하거나 컬렉션을 사용할 때 equals()와 hashCode()는 무조건 구현되어야 하는 기본 중의 기본입니다.
특히 실무에서는 이 메서드들이 올바르게 정의되어야만 중복 체크, 검색, 삭제, 저장 등 모든 로직이 정확하게 동작합니다.이 글을 통해 두 메서드의 개념과 실무 적용법을 확실히 익히셨길 바랍니다. "객체 비교가 어렵다"는 고민은 이제 끝! 지금 코드를 작성하며 직접 적용해보세요.
한 줄 요약: "equals()는 내용 비교, hashCode()는 위치 지정! 둘은 항상 함께 구현해야 한다."
'이직&취업 > Java 기초 상식' 카테고리의 다른 글
스택 (Stack)과 큐 (Queue)에 대해 알아보자! (22) | 2025.04.12 |
---|---|
REST API란 무엇이고 어떻게 만드나요? (24) | 2025.04.09 |
ArrayList와 LinkedList의 차이점은 무엇인가요? (29) | 2025.04.08 |
자바에서 static 키워드는 어떤 역할을 하나요? (40) | 2025.04.08 |
스트림(Stream) API를 어떻게 활용하나요? (27) | 2025.04.07 |