java의 record 타입이 궁금하다면 37편. 레코드(Record) 이 글을 추천합니다.
Java의 기본 클래스(+ Lombok)로 만들어진 DTO를 record 타입으로 변환하다 필드에 기본값을 설정해야 하는 상황을 마주했다. 기존 DTO 클래스는 아래와 같다.
@Builder
@Getter
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class CarsDto {
@Builder.Default
List<String> values = new ArrayList<>();
Integer speed = 10;
}
Lombok의 @Builder.Default를 사용하여 빌더에서 값을 지정하지 않은 경우에 기본값이 들어가도록 해두었다.
하지만 record 타입에서는 @Builder.Default를 사용하는 방식으로는 기본값을 설정해 줄 수가 없었다. 이를 해결하기 위해 컴팩트 생성자를 사용해 보았다.
컴팩트(Compact) 생성자
public record RecordCarsDto(List<String> values, int speed) {
@Builder
public RecordCarsDto { // 매개변수를 받는 부분이 생략됨
if (Objects.isNull(values)) {
values = new ArrayList<>();
}
if (Objects.isNull(speed)) {
speed = 10;
}
// this.values = values; this.speed = speed; 와 같은 초기화 로직은 마지막에 자동으로 호출해줌.
}
}
컴팩트 생성자는 필드 초기화 외의 추가적인 초기화 로직이 필요한 경우 사용할 수 있다. 일반적인 표준(Canonical) 생성자와 다른 점은 매개변수를 받는 부분이 생략된 형태라는 것이다.
또한 this.valuse = values와 같이 개발자가 일일이 필드를 초기화하는 로직을 작성하지 않아도 마지막에 자동으로 필드를 초기화해 준다. 그래서 생성자에서 this.XXX로 필드에 접근이 불가능하다. 아직 초기화되기 전이기 때문이다.
그리고 필드들이 private final임에도 컴팩트 생성자에서 사용하는 변수는 지역 변수라 값을 여러 번 할당할 수 있으며 마지막에 할당한 값으로 초기화된다.
public record RecordCarsDto(List<String> values, int speed) {
@Builder
RecordCarsDto(List<String> values, int speed) {
if (Objects.isNull(values)) {
values = new ArrayList<>();
}
if (Objects.isNull(speed)) {
speed = 10;
}
this.values = values;
this.speed = speed;
}
}
마지막으로 정리하자면 컴팩트 생성자를 사용한 코드는 위 코드처럼 작동한다. 이처럼 컴팩트 생성자에서 null 검사를 하고 기본값을 할당해주는 로직을 추가하여 Lombok의 @Builder.Default 처럼 동작하도록 만들 수 있다.
사실 일일이 null 인지를 검증해서 null 인 경우에만 기본값을 넣어주는 방식이 썩 마음에 들지는 않는다. 하지만 java17에서 record를 공식적으로 지원하고 있는 상황에서 굳이 Lombok을 쓰는 것보다는 이게 나은 것 같다. 자꾸 Kotlin이 그리워지는 건 기분 탓이겠지..🥲