java @Builder 기능 더 활용하기 toBuilder(), @Singular, @Builder.Default
java에서는 클래스를 객체화하기 위해 '점층적 생성자 패턴(Telescoping Constructor Pattern)'의 안전성과 '자바 빈즈 패턴(Java Beans Pattern)'의 가독성을 더한 '빌더 패턴(Builder Pattern)'을 주로 사용하는데요.
@Builder // 외 생략
public class Order {
...
}
위 코드와 같이 Lombok을 사용하면 직접 빌더 관련 코드를 구현할 필요 없이 @Builder 어노테이션만 적용하여 빌더 패턴을 사용할 수 있으며, 대부분 이러한 방식으로 빌더를 자주 사용하고 계실 거라고 생각됩니다.
해당 포스팅에서는 기본적인 builder의 기능을 조금 더 활용할 수 있는 toBuilder(), @Singular, @Builder.Default에 대해서 살펴보겠습니다.
1. toBuilder()
@Builder 어노테이션에는 다음과 같이 'toBuilder' 속성을 지정할 수 있는데요. (default = false)
해당 속성을 지정하게 되면 아래와 같이 toBuilder() 메서드를 사용할 수 있으며, 해당 메서드는 기존 객체 인스턴스의 값으로 초기화되는 필더를 가져옵니다.
때문에 toBuilder() 메서드는 아래와 같이 기존의 인스턴스를 기반으로 builder를 사용하여 일부 값을 변경할 때 활용할 수 있습니다.
@Getter
@Builder(toBuilder = true)
@AllArgsConstructor
public class Order {
private String orderNumber;
private String productName;
private Long totalPrice;
}
(예시를 위한 Order class)
//builder를 통한 객체 인스턴스 생성
Order order = Order.builder()
.orderNumber("order123")
.productName("something")
.totalPrice(1000L)
.build();
System.out.println("order: " + order);
//order: Order(orderNumber=order123, productName=something, totalPrice=1000)
//toBuilder() 메서드를 사용했을 때 OrderBuilder를 반환
Order.OrderBuilder orderBuilder = order.toBuilder();
//toBuilder()
Order updateOrder = order.toBuilder()
.totalPrice(2000L)
.build();
System.out.println("updateOrder: " + updateOrder);
//updateOrder: Order(orderNumber=order123, productName=something, totalPrice=2000)
(toBuilder 사용 예시)
2. @Singular
@Getter
public class Order {
private String orderNumber;
private String productName;
private Long totalPrice;
private List<String> stringList;
private Map<String, String> stringMap;
@Builder
public Order(String orderNumber, String productName, Long totalPrice,
@Singular("stringListItem") List<String> stringList,
@Singular("stringMapItem") Map<String, String> stringMap) {
this.orderNumber = orderNumber;
this.productName = productName;
this.totalPrice = totalPrice;
this.stringList = stringList;
this.stringMap = stringMap;
}
(예시를 위한 Order class)
Order order = Order.builder()
.orderNumber("order123")
.productName("something")
.totalPrice(1000L)
//@Singular("stringListItem")
.stringListItem("list1")
.stringListItem("list2")
.stringListItem("list3")
//@Singular("stringMapItem")
.stringMapItem("key1", "value1")
.stringMapItem("key2", "value2")
.stringMapItem("key3", "value3")
.build();
}
(@Singular 사용 예시)
lombok v1.16.8부터 추가된 '@Singular' 어노테이션은 @Builder와 함께 사용되며, 위 예시와 같이 Collection에 대한 단일 요소를 추가하는 메서드를 작성하기 위해 적용됩니다.
3. @Builder.Default
@ToString
@Getter
@Builder
@AllArgsConstructor
public class Order {
private String orderNumber;
private String productName;
private Long totalPrice;
private int quantity;
}
(Order class)
Order order = Order.builder().build();
System.out.println("order: " + order);
//order: Order(orderNumber=null, productName=null, totalPrice=null, quantity=0)
builder는 값을 설정하지 않으면 기본적으로 null 또는 0을 채워주는데요.
이때 직접 기본값을 설정해주고 싶다면 아래 예시와 같이 '@Builder.Default'를 사용할 수 있습니다.
(Default는 lombok v1.16.16에서 추가된 기능입니다.)
@ToString
@Getter
@Builder
@AllArgsConstructor
public class Order {
private String orderNumber;
private String productName;
@Builder.Default
private Long totalPrice = 100L;
@Builder.Default
private int quantity = 1;
}
(@Builder.Default 사용 예시)
Order order = Order.builder().build();
System.out.println("order: " + order);
//order: Order(orderNumber=null, productName=null, totalPrice=100, quantity=1)
(기본 값이 설정되어 출력되는 것을 확인할 수 있습니다.)
***
하지만 현재 @Builder.Default 사용 시에 아래와 같은 문제점도 있는데요.
위 예시의 Order 클래스와 같이 @Builder 어노테이션을 클래스 레벨에서 적용했을 때, 컴파일된 Order.class를 살펴보면 다음과 같이 Default 기능이 적용된 필드에 대해 set 여부를 확인하여 set이 되지 않은 경우 기본 값을 적용해 주는 것을 볼 수 있는데요.
@ToString
@Getter
public class Order {
private String orderNumber;
private String productName;
@Builder.Default
private Long totalPrice = 100L;
@Builder.Default
private int quantity = 1;
@Builder
public Order(String orderNumber, String productName, Long totalPrice, int quantity) {
this.orderNumber = orderNumber;
this.productName = productName;
this.totalPrice = totalPrice;
this.quantity = quantity;
}
}
(Default 기능이 작동하지 않는 경우)
order: Order(orderNumber=null, productName=null, totalPrice=null, quantity=0)
(값을 세팅하지 않고 builder를 사용한 인스턴스 생성 결과)
Order 클래스에 @Builder를 다음과 같이 사용했을 때는 위에서 컴파일된 Order.class와 다르게 set 여부 확인 및 기본 값을 넣어주는 부분이 없다는 것을 볼 수 있었는데요.
해당 문제에 대해서는 아래 lombok project의 github에서도 관련 내용이 계속해서 이슈가 되고 있다는 것을 찾아볼 수 있었습니다.
https://github.com/projectlombok/lombok/issues/1347
< 관련 자료 >
2021.08.22 - [Programming/Java] - Java 빌더 패턴 (Builder Pattern)이란?
2021.08.21 - [Programming/Java] - (Java) 점층적 생성자 패턴 & 자바 빈즈 패턴
'Programming > Java' 카테고리의 다른 글
Java 운영체제(윈도우, 리눅스) 프로세스 상태 확인하는 방법 (0) | 2024.02.11 |
---|---|
SOLID 객체 지향 프로그래밍의 5가지 원칙 (0) | 2023.08.14 |
(java) transient volatile 키워드는 무엇인가 (1) | 2023.08.02 |
java 임시 비밀번호 생성(SecureRandom 사용하는 이유) (0) | 2023.07.30 |
Java Map 반복시키는 가장 효율적인 방법 (0) | 2023.07.26 |