개발/Lombok
@Builder
함수형 인간
2025. 3. 11. 17:57
@Builder는 롬복(Lombok)에서 제공하는 어노테이션으로, 디자인 패턴 중 하나인 빌더 패턴(Builder Pattern)을 자동으로 생성해준다.
빌더 패턴이란?
객체 생성 과정이 복잡하거나, 선택적 매개변수가 많을 때 유용한 디자인 패턴. 객체를 직접 생성하는 대신, 빌더 객체를 통해 단계별로 속성 값을 설정하고 마지막에 최종 객체를 생성하는 방식.
@Builder의 주요 기능:
- 빌더 클래스 자동 생성: @Builder를 클래스, 생성자, 또는 정적 메서드에 붙이면 롬복이 자동으로 빌더 클래스를 생성.
- builder() 메서드 제공: 생성된 빌더 클래스의 인스턴스를 반환하는 builder() 정적 메서드를 자동으로 생성.
- 필드 설정 메서드: 빌더 클래스 내에 각 필드에 대한 설정 메서드(setter와 유사하지만 메서드 체이닝을 지원)를 자동으로 생성.
- build() 메서드: 설정이 완료된 객체를 생성하는 build() 메서드를 빌더 클래스에 자동으로 생성.
- 메서드 체이닝 지원: 필드 설정 메서드들이 this (빌더 객체 자신)를 반환하므로, 메서드 체이닝을 통해 간결하게 객체를 생성.
- @Singular과 함께 사용하여 컬렉션 처리 (선택 사항): List, Set, Map과 같은 컬렉션 필드에 @Singular 어노테이션을 함께 사용하면, 빌더에서 컬렉션에 요소를 하나씩 추가하는 메서드(add 접두사)를 제공.
- @Builder.Default를 사용하여 기본 값 할당 (선택사항, 롬복 1.18.2 이상): 필드 선언시 초기화하는 값이 builder에서도 기본 값으로 유지되게 함.
동작 방식:
- @Builder 어노테이션이 붙은 클래스(또는 생성자, 정적 메서드)를 롬복이 인식.
- 롬복은 해당 클래스 내부에 [ClassName]Builder라는 이름의 빌더 클래스를 생성. (클래스에 적용했을 경우)
- 빌더 클래스에는 원본 클래스의 각 필드에 대한 private 필드가 생성.
- 빌더 클래스에는 각 필드에 값을 설정하는 메서드(메서드 체이닝 지원)가 생성.
- 빌더 클래스에는 build() 메서드가 생성되어, 설정된 값을 바탕으로 원본 클래스의 객체를 생성하여 반환.
- 원본 클래스에 builder() 정적 메서드가 생성되어, 빌더 클래스의 인스턴스를 반환.
사용 예시 (클래스에 적용):
Java
import lombok.Builder;
import lombok.Getter;
import lombok.Singular;
import java.util.List;
@Builder
@Getter // Getter는 빌더와는 별개이지만, 생성된 객체의 값을 확인하기 위해 자주 함께 사용됩니다.
public class User {
private String name;
private int age;
@Singular
private List<String> hobbies;
@Builder.Default
private boolean active = true;
}
// 객체 생성 예시:
public class Main {
public static void main(String[] args) {
User user = User.builder()
.name("John Doe")
.age(30)
.hobby("reading") // @Singular 덕분에 add + 필드명 형태
.hobby("coding") // 여러개 추가 가능.
// .active(false) // @Builder.Default에 의해 true가 기본값이므로 생략가능.
.build();
System.out.println(user.getName());
System.out.println(user.getHobbies());
System.out.println(user.isActive());
}
}
사용 예시 (생성자에 적용):
Java
import lombok.Builder;
import lombok.Getter;
@Getter
public class Product {
private String name;
private double price;
private int stock;
@Builder // 생성자에 적용
public Product(String name, double price, int stock) {
this.name = name;
this.price = price;
this.stock = stock;
}
}
// 객체 생성 예시:
// Product.builder()... (생성자에 @Builder를 적용해도 동일하게 사용)
사용 예시 (정적 팩토리 메서드에 적용):
Java
import lombok.*;
@Value
public class Mail {
private final String recipient;
private final String title;
private final String body;
@Builder(builderMethodName = "create") //정적 팩토리 메서드 이름과 다르게 설정
public static Mail of(String recipient, String title, String body) {
return new Mail(recipient, title, body);
}
}
//객체 생성
//Mail mail = Mail.create()...
장점:
- 가독성 향상: 객체 생성 코드가 훨씬 명확해지고, 어떤 필드에 어떤 값이 설정되는지 한눈에 파악하기 쉽다.
- 유연성 증가: 선택적 매개변수가 많은 경우에도 깔끔하게 객체를 생성할 수 있다.
- 불변성(Immutability) 지원: 빌더 패턴은 불변 객체를 생성하는 데 유용. (모든 필드를 final로 선언하고, setter를 제공하지 않으면 불변 객체가 된다.)
- 코드 중복 감소: 빌더 클래스 생성 및 관련 메서드 작성을 롬복이 대신해주므로, 코드 양이 줄어든다.
- 유지보수 용이: 필드가 추가/삭제되더라도 빌더 관련 코드는 롬복이 자동으로 관리해주므로, 유지보수가 간편.
주의 사항:
- 모든 필드를 빌더를 통해 설정해야 하는 것은 아닙니다. 필수적인 필드는 생성자에 직접 전달하고, 선택적인 필드만 빌더를 통해 설정하는 것이 더 좋은 설계일 수 있다.
- 상속 관계에서 @Builder를 사용할 때는 주의가 필요. 하위 클래스에서 @Builder를 사용할 때 상위 클래스의 필드를 함께 빌드하려면 @SuperBuilder를 사용하거나, 상위 클래스의 빌더를 명시적으로 호출해야 한다.
- 빌더 패턴 자체가 객체 생성 로직을 분리하는 것이므로, 객체 생성 과정이 매우 단순한 경우에는 오히려 코드가 더 복잡해질 수 있다. 이런 경우에는 빌더 패턴을 사용하지 않는 것이 더 나을 수 있다.