개발을 하다 보면 객체 간의 동등성을 비교해야 할 때가 많습니다. 하지만 Java의 기본 equals()와 hashCode()는 메모리 주소를 기준으로 동등성을 판단하기 때문에, 우리가 원하는 대로 작동하지 않을 수 있습니다.
예를 들어, 다음 두 객체는 내용은 같지만 기본 equals()로 비교하면 다르다고 나옵니다:
Person p1 = new Person("John", 30);
Person p2 = new Person("John", 30);
System.out.println(p1.equals(p2)); // false
이 문제를 해결하려면 equals()와 hashCode()를 재정의해야 합니다. 하지만 직접 작성하면 코드가 길어지고 유지보수도 어렵습니다. 이런 번거로움을 해결하기 위해 등장한 것이 바로 Lombok의 @EqualsAndHashCode입니다.
import lombok.EqualsAndHashCode;
@EqualsAndHashCode
public class Person {
private String name;
private int age;
}
위 코드는 자동으로 equals()와 hashCode()를 생성합니다. 이렇게 하면 객체의 내용을 기준으로 동등성을 비교할 수 있습니다.
Person p1 = new Person("John", 30);
Person p2 = new Person("John", 30);
System.out.println(p1.equals(p2)); // true
기존에 직접 작성해야 했던 반복적인 코드를 간단히 어노테이션 하나로 대체한 것입니다.
그리고 또한, 이 어노테이션은 필드를 선택적으로 제외하거나 상속 관계에서도 유용하게 활용할 수 있습니다.
특정 필드 제외하기
때로는 객체의 일부 필드를 동등성 비교에서 제외해야 할 때가 있습니다. 예를 들어, 사용자의 비밀번호는 비교 대상이 되어서는 안 됩니다.
@EqualsAndHashCode(exclude = "password")
public class User {
private String username;
private String password;
}
이렇게 하면 password는 equals()와 hashCode()에 포함되지 않습니다.
상속 구조에서 활용하기
상속 관계에서도 @EqualsAndHashCode는 매우 유용합니다. 예를 들어, DTO 클래스가 상속 구조를 가지는 경우 부모 클래스의 필드까지 동등성 비교에 포함할 수 있습니다.
import lombok.EqualsAndHashCode;
@EqualsAndHashCode
public class BaseDto {
private Long id;
}
@EqualsAndHashCode(callSuper = true)
public class ChildDto extends BaseDto {
private String name;
}
위 예제에서 callSuper = true를 설정하면 부모 클래스의 id 필드도 동등성 비교에 포함됩니다.
그리고 또 다른 장점은, 이 어노테이션이 JPA와 연동될 때 발생할 수 있는 영속성 문제를 해결할 수 있다는 것입니다.
JPA와의 호환성
JPA Entity에서 equals()와 hashCode()는 신중히 사용해야 합니다. 특히 id 필드가 자동 생성되는 경우, 잘못된 동등성 비교가 발생할 수 있습니다.
//문제 예시코드
@Entity
@EqualsAndHashCode
public class User {
@Id
private Long id;
private String name;
}
위 코드에서 id는 데이터베이스에서 생성됩니다. 하지만 아직 영속화되지 않은 상태에서는 id가 null이므로, 동등성 비교가 정확하지 않을 수 있습니다.
// 해결 방법
@Entity
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class User {
@Id
@EqualsAndHashCode.Include
private Long id;
private String name;
}
onlyExplicitlyIncluded = true를 사용하면 비교 대상 필드를 명시적으로 지정할 수 있습니다. 이를 통해 JPA와의 호환성을 유지하면서도 동등성 비교를 안전하게 처리할 수 있습니다.
왜 @EqualsAndHashCode를 사용해야 할까?
@EqualsAndHashCode는 반복적인 코드 작성을 줄이고, 객체 비교와 관련된 문제를 간단히 해결해 줍니다.
- 효율성: 반복적인 equals()와 hashCode() 구현 제거.
- 유연성: 상속, JPA 호환성, 필드 제외 등 다양한 설정 가능.
- 가독성: 간결한 코드로 가독성과 유지보수성 향상.
하지만, 사용하지 않으면 발생할 수 있는 문제들도 존재합니다.
사용하지 않으면 발생하는 문제
- 객체 동등성 비교 오류:
기본 equals()는 메모리 주소를 비교하므로, 내용이 같아도 다르다고 판단합니다. - Hash 컬렉션 문제:
hashCode()가 구현되지 않으면 HashSet이나 HashMap에서 중복 객체를 정확히 구분할 수 없습니다. - 상속 구조에서 비교 누락:
상속 관계에서 부모 클래스의 필드를 포함하지 않으면, 비교 결과가 불완전할 수 있습니다.
단점은 없나??
@EqualsAndHashCode는 코드 작성 시간을 줄이고 가독성을 높이는 데 유용하지만, 다음과 같은 단점이 있습니다:
- 성능 이슈
객체의 필드가 많거나, 필드에 복잡한 데이터 구조(List, Map 등)가 포함된 경우, equals()와 hashCode() 호출이 성능에 영향을 미칠 수 있습니다. - 순환 참조로 인한 StackOverflowError.
클래스가 순환 참조 구조를 가질 경우, @EqualsAndHashCode는 StackOverflowError를 유발할 수 있습니다. - JPA와 함께 사용 시 주의 필요.
id와 같은 영속성 관련 필드를 비교 대상으로 포함하면, 영속화되지 않은 상태에서 잘못된 동등성 판단을 할 수 있습니다. - 메서드 생성의 불투명성.
@EqualsAndHashCode는 메서드 생성 과정을 숨기기 때문에, 메서드가 어떤 방식으로 생성되었는지 파악하기 어려울 수 있습니다. 이로 인해 디버깅 시 혼란이 생길 가능성이 있습니다. - 상속 관계에서 잘못된 비교 가능성.
상속 관계에서 callSuper = true를 사용하지 않으면, 부모 클래스 필드가 비교에서 누락됩니다. 반대로 callSuper = true를 사용하면 모든 상위 클래스의 필드를 비교하므로, 예상치 못한 성능 문제가 발생할 수 있습니다.
결론
Lombok의 @EqualsAndHashCode는 객체 비교에서 발생할 수 있는 다양한 문제를 간단히 해결합니다. 특히, 상속 관계와 JPA와의 연동에서도 강력한 도구가 될 수 있습니다.
이 어노테이션을 사용하면 코드의 유지보수성을 높이고, 동등성 비교와 관련된 실수를 줄일 수 있습니다.
"반복하지 말라(Don’t Repeat Yourself)"는 개발의 핵심 원칙 중 하나입니다. @EqualsAndHashCode는 이를 실천할 수 있는 도구 중 하나입니다.
'Spring' 카테고리의 다른 글
Spring Boot에서 H2 Console 'localhost에서 연결을 거부했습니다' 에러 해결 (1) | 2024.12.06 |
---|---|
[Spring] Optional의 활용: Repository, Service, 그리고 ResponseEntity의 역할 (0) | 2024.12.06 |
JPA에서 @Modifying 사용법과 동작 원리 (0) | 2024.12.03 |
Spring에서 @RequiredArgsConstructor 사용법과 장점 (0) | 2024.12.03 |
spring boot 버전에 호환되는 의존성 버전 확인하는 법 (0) | 2024.11.29 |