본문 바로가기

PROGRAMMING/SPRING

[SPRING] @EqualsAndHashCode(callSuper = true)

@EqualsAndHashCode(callSuper = true)

Lombok 라이브러리에서 제공하는 기능으로 

부모 클래스의 equalshashCode 메서드를 포함하여 자식 클래스의 해당 메서드를 생성하는 역할을 한다.

 

Lombok 의존성 추가

build.gradle

dependencies {
   compileOnly 'org.projectlombok:lombok'
   annotationProcessor 'org.projectlombok:lombok'
}

 

사용 예제

PrivateRequestDto.java

 import lombok.Builder;
 import lombok.Data;
 import org.springframework.web.reactive.function.server.ServerRequest;
 
 @Data
 public class PrivateRequestDto {
     private String apiKey;
     private String secretKey;
     private String orderCurrency;
     private String paymentCurrency;
 
     @Builder
     public PrivateRequestDto(String apiKey, String secretKey, String orderCurrency, String paymentCurrency) {
         this.apiKey = apiKey;
         this.secretKey = secretKey;
         this.orderCurrency = orderCurrency;
         this.paymentCurrency = paymentCurrency;
     }

 

위와 같은 api request 값을 매핑하는 dto가 있을 때 

apiKey와 secretKey는 다른 요청에서도 중복으로 필요한 컬럼이여서 ApiCredentialsDto로 분리하려 했다.

 

ApiCredentialsDto.java

import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
public class ApiCredentialsDto {
    private String apiKey;
    private String secretKey;

    public ApiCredentialsDto(String apiKey, String secretKey) {
        this.apiKey = apiKey;
        this.secretKey = secretKey;
    }
}

 

그리고 처음 PrivateRequestDto를 아래와 같이 변경했다.

 

변경한 PrivateRequestDto.java

import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.web.reactive.function.server.ServerRequest;

@Data
@EqualsAndHashCode(callSuper = true)
public class PrivateRequestDto extends ApiCredentialsDto {
    private String orderCurrency;
    private String paymentCurrency;

    @Builder
    public PrivateRequestDto(String apiKey, String secretKey, String orderCurrency, String paymentCurrency) {
        super(apiKey, secretKey);
        this.orderCurrency = orderCurrency;
        this.paymentCurrency = paymentCurrency;
    }

    public static PrivateRequestDto fromRequest(ServerRequest request) {
        return PrivateRequestDto.builder()
                .apiKey(request.headers().firstHeader("api-key"))
                .secretKey(request.headers().firstHeader("api-secret"))
                .orderCurrency(request.queryParam("order-currency").orElse(""))
                .paymentCurrency(request.queryParam("payment-currency").orElse(""))
                .build();
    }
}

 

이 때 @EqualsAndHashCode(callSuper = true) 어노테이션을 붙이는 이유는

상속받은 필드들까지 포함한 동등성 비교와 해시 코드를 생성하기 위함이다.

 

테스트를 해보면

import prj.blockchain.bithumb.dto.PrivateTradeRequestDto;

import java.util.HashSet;
import java.util.Set;

public class Test {
    public static void main(String[] args) {
        PrivateTradeRequestDto dto1 = PrivateTradeRequestDto.builder()
                .apiKey("api-key-1")
                .secretKey("secret-key-1")
                .orderCurrency("BTC")
                .paymentCurrency("USD")
                .build();

        PrivateTradeRequestDto dto2 = PrivateTradeRequestDto.builder()
                .apiKey("api-key-1")
                .secretKey("secret-key-1")
                .orderCurrency("BTC")
                .paymentCurrency("USD")
                .build();

        Set<PrivateTradeRequestDto> set = new HashSet<>();
        set.add(dto1);

        System.out.println("Contains dto2: " + set.contains(dto2)); // 예상 출력: true, 실제 출력: true
    }
}

 

위의 코드에서는 dto1dto2가 동일한 apiKeysecretKey 값을 가지고 있지만,

hashCodeequals 메서드가 부모 클래스의 필드를 포함하지 않기 때문에 HashSet은 두 객체를 서로 다른 객체로 인식한다.

고로 예상 되는 출력값은 true였지만 실제론 false 다른 객체라고 나온다.

 

결론

  • @EqualsAndHashCode(callSuper = true)를 사용하면
    부모 클래스의 필드들까지 포함하여 equalshashCode 메서드를 생성하므로, 상속 구조에서도 올바른 동등성 비교가 가능하다.
  • 이를 통해 HashSet과 같은 자료구조에서 객체를 정확하게 비교하고, 예상치 못한 동작을 방지할 수 있다.