[JAVA] equals()와 hashCode() 메서드
Java에서 equals()
와 hashCode()
메서드는 객체의 동등성 비교와 해시 기반 컬렉션 사용에 매우 중요한 역할을 다. 이 두 메서드를 제대로 이해하고 구현하지 않으면 예상치 못한 버그가 발생할 수 있다.
이번 글에서는 두 메서드의 올바른 구현 방법과 주의사항에 대해 알아보자.
equals() 메서드
equals() 메서드의 기본 규칙
equals()
메서드는 다음 다섯 가지 규칙을 만족해야 합니다:
- 반사성: x.equals(x)는 항상 true
- 대칭성: x.equals(y)가 true이면 y.equals(x)도 true
- 추이성: x.equals(y)가 true이고 y.equals(z)가 true이면 x.equals(z)도 true
- 일관성: x.equals(y)를 여러 번 호출해도 항상 같은 결과
- null 비교: x.equals(null)은 항상 false
잘못된 구현의 예
public class User {
private String name;
private int age;
// 잘못된 구현 예시
@Override
public boolean equals(Object obj) {
if (obj instanceof User) {
User other = (User) obj;
return name.equals(other.name); // age 필드 무시
}
return false;
}
// hashCode() 미구현
}
올바른 구현 예시
public class User {
private String name;
private int age;
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
User other = (User) obj;
return age == other.age &&
Objects.equals(name, other.name);
}
}
hashCode() 메서드
hashCode()의 규칙
- 같은 객체의 hashCode()는 여러 번 호출해도 동일한 값 반환
- equals()가 true인 두 객체의 hashCode()는 동일
- equals()가 false인 두 객체의 hashCode()는 같을 수 있으나, 다른 것이 성능상 유리
잘못된 구현의 예
public class User {
private String name;
private int age;
@Override
public int hashCode() {
return name.hashCode(); // age 필드 무시
}
}
올바른 구현 예시
public class User {
private String name;
private int age;
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
equals와 hashCode의 관계
HashSet/HashMap 사용 시 주의사항
public class Example {
public static void main(String[] args) {
Set<User> users = new HashSet<>();
User user1 = new User("John", 30);
User user2 = new User("John", 30);
users.add(user1);
users.add(user2);
System.out.println(users.size()); // equals/hashCode 구현에 따라 1 또는 2
}
}
실제 문제 사례
// 문제가 될 수 있는 상황
Map<User, String> userMap = new HashMap<>();
User user = new User("John", 30);
userMap.put(user, "some value");
user.setAge(31); // 객체 상태 변경
String value = userMap.get(user); // null이 반환될 수 있음
올바른 구현 방법
완전한 구현 예시
public class User {
private final String name;
private final int age;
public User(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;
User other = (User) obj;
return age == other.age &&
Objects.equals(name, other.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
Lombok 활용
@EqualsAndHashCode 사용
import lombok.EqualsAndHashCode;
@EqualsAndHashCode
public class User {
private final String name;
private final int age;
// 생성자만 구현하면 됨
}
특정 필드 제외
@EqualsAndHashCode(exclude = {"temporaryField"})
public class User {
private final String name;
private final int age;
private String temporaryField; // equals/hashCode 계산에서 제외
}
성능 고려사항
hashCode() 캐싱
public class User {
private final String name;
private final int age;
private int hashCode; // 해시코드 캐싱
@Override
public int hashCode() {
int result = hashCode;
if (result == 0) {
result = Objects.hash(name, age);
hashCode = result;
}
return result;
}
}
필드 비교 순서 최적화
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
User other = (User) obj;
// 비교 비용이 적은 필드부터 비교
return age == other.age && // 기본 타입 먼저
Objects.equals(name, other.name); // 객체 타입 나중에
}
equals()
와 hashCode()
의 올바른 구현은 Java 애플리케이션의 정확성과 성능에 큰 영향을 미친다.
equals()
와hashCode()
는 반드시 함께 구현- 불변 객체를 사용하면 해시 기반 컬렉션에서 더 안전
- 가능하면 Lombok의
@EqualsAndHashCode
활용 - 성능이 중요한 경우 hashCode 캐싱 고려
- IDE의 자동 생성 기능을 활용하되, 생성된 코드 검토 필요
'개발&프로그래밍' 카테고리의 다른 글
[JAVA] try-with-resources와 AutoCloseable 인터페이스 (0) | 2024.11.04 |
---|---|
[JAVA] 직렬화(Serializable)와 역직렬화 (6) | 2024.11.03 |
[JAVA] Stream의 map과 flatMap 차이 (0) | 2024.11.01 |
[JAVA] Java 컬렉션 프레임워크 성능 비교 (ArrayList vs LinkedList vs HashSet) (1) | 2024.10.24 |
[JAVA] Java에서 NullPointerException을 방지하는 팁 (1) | 2024.10.23 |
댓글