[JAVA]@Builder(@Builder.default)

윤재열·2022년 3월 29일
1

Java

목록 보기
51/71

@Builder

  • Lombok에서 제공하는 이 어노테이션은 생성자 인자를 메서드 체인을 통해 명시적으로 대입하여 생성자를 호출할 수 있게 빌더 클래스를 생성 해준다. 빌더 클래스와 IDE의 자동 완성 기능을 같이 활용하면 생성자 작성 시 오기입 확률과 인자를 누락할 확률을 획기적으로 낮출 수 있습니다.

  • doc을 보면 @Builder는 생성자, 메서드 또는 클래스 레벨에서 쓰일 수 있다고 설명되어 있다. 또한 클래스 레벨에서 쓰일 경우 기본적으로 전체 멤버를 생성자의 매개값으로 갖는 private 생성자를 만들어 준다. 이 생성자는 @XArgsConstructor(NoArgs, RequiredArgs) 또는 어떤 생성자도 클래스 내부에 선언하지 않았을 경우에만 생성된다. 반대로 위의 두 조건 중 하나를 했을 경우, 모든 필드를 매개값으로 하는 생성자를 자동으로 선언해서 사용한다. 따라서 이 경우 All Args Constructor가 없으면 컴파일 에러가 발생합니다.

  • 정리하면 @Builder 클래스 레벨에서 쓰려면 All args constructor가 있어야 한다. 이 외의 생성자는 컴파일 에러를 일으킵니다.

  • 예를 들어 @NoArgsConstructor 를 쓰고 클래스 레벨의 @Builder를 쓰게 되면 All args constructor 없이 기본 생성자만 선언한 것과 같으므로 컴파일 에러가 뜹니다.

  • 그런데 JPA나 json parser와 같은 라이브러리를 쓸 때에는 반드시 클래스에 기본 생성자가 있어야 한다. 이 경우 @NoArgsConstructor를 쓸 수 밖에 없다. 그러면 클래스 레벨에서 @Builder를 쓸 수가 없어집니다. 방법은 전체 필드를 사용하는 생성자를 직접 선언하고 그 생성자에 @Builder 어노테이션을 쓰든가, 아니면 @NoArgsConstructor@AllArgsConstructor를 모두 쓰면 됩니다.

@Bulder.Default

@ToString
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class BuildEx {

   private String name;
   private String phoneNum;
   private List<Example> example = new ArrayList<Example>();

}
public class BuildExApp{
	
    public static void main(String[] args){
    BuildEx buildEx =BuildEx.builder()
    						.name("재열")
                            .phoneNum("010-1234-5678")
                            .build();
	System.out.println(buildEx.toString());
    }
    }
  • 위의 예시처럼 필드 이름을 명시적으로 넣을 수 있어서 생성자 오버로딩 사용시 시그니처를 신경쓸 필요가 없습니다.

결과

BuildEx(name=재열, phoneNum=010-1234-5678, example=null)
  • 여기서 빌터 패턴을 통해 인스턴스를 만들 때 특정 필드를 초기화 하고 싶다면 @Builder.Default를 쓰면 됩니다.
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class BuildEx {
	
    @Builder.Default
    private String name="재열";
    private String phoneNum;
    private List<Example> example = new ArrayList<Example>();

}
public class BuildExApp{
	
    public static void main(String[] args){
    BuildEx buildEx =BuildEx.builder()
                            .phoneNum("010-1234-5678")
                            .build();
	System.out.println(buildEx.toString());
    }
    }

결과

BuildEx(name=재열,phoneNum=010-1234-5678, example=null)
  • 이렇게 객체를 원하는 값으로 초기화해서 반환 받을 수 있습니다.

빌더와 생성자생성의 차이

public class BuildExApp{
	
    public static void main(String[] args){
    
    	BuildEx buildEx = BuildEx.builder().phoneNum("010-1234-5678").build();
        System.out.println(buildEx.toString());
        
        BuildEx buildEx1 = new BuildEx();
        System.out.println(buildEx1.toString());

결과

BuildEx(name=재열, phoneNum=010-4623-2891,example=null)
BuildEx(name=재열, phoneNum=null,example=[])
  • 빌더를 통해 만든 객체는 List 필드가 null로 초기화 되었고, 빌더 없이 기본 생성자로 생성한 pojo1의 List 필드는 정상적으로 empty List로 초기화되었다. 클래스에서는 분명히 new ArrayList<>로 초기화를 했는데 왜 이런걸까?

  • doc을 살펴보면 빌더는 아래와 같이 코드를 만들어준다.

class Example<T> {
   	private T foo;
   	private final String bar;
   	
   	private Example(T foo, String bar) {
   		this.foo = foo;
   		this.bar = bar;
   	}
   	
   	public static <T> ExampleBuilder<T> builder() {
   		return new ExampleBuilder<T>();
   	}
   	
   	public static class ExampleBuilder<T> {
   		private T foo;
   		private String bar;
   		
   		private ExampleBuilder() {}
   		
   		public ExampleBuilder foo(T foo) {
   			this.foo = foo;
   			return this;
   		}
   		
   		public ExampleBuilder bar(String bar) {
   			this.bar = bar;
   			return this;
   		}
   		
   		@java.lang.Override public String toString() {
   			return "ExampleBuilder(foo = " + foo + ", bar = " + bar + ")";
   		}
   		
   		public Example build() {
   			return new Example(foo, bar);
   		}
   	}
   }
  • 필드를 사용하는 생성자와 각 필드의 setter 메서드로 구성된 inner 클래스를 하나 만들어서 그 안에서 원본 클래스의 인스턴스를 리턴한다. 여기서 객체 타입의 필드가 있다고 가정하면, 당연히 내부 클래스에서는 이 객체 타입을 초기화하는 코드가 없다. 따라서 null로 초기화 될 것이고, 이 필드를 포함해서 원본 클래스가 만들어지므로 빌더를 통한 객체 생성에서는 객체 타입 필드가 null인 것이다.
  • 이를 해결하기 위해서도 역시 @Builder.Default를 쓰면 됩니다.
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class BuildEx {
	
    @Builder.Default
    private String name="재열";
    private String phoneNum;
    @Builder.Default
    private List<Example> example = new ArrayList<Example>();

}
public class BuildExApp{
	
    public static void main(String[] args){
    
    	BuildEx buildEx = BuildEx.builder().phoneNum("010-1234-5678").build();
        System.out.println(buildEx.toString());
        
        BuildEx buildEx1 = new BuildEx();
        System.out.println(buildEx1.toString());

결과

```java
BuildEx(name=재열, phoneNum=010-4623-2891,example=[])
BuildEx(name=재열, phoneNum=null,example=[])
profile
블로그 이전합니다! https://jyyoun1022.tistory.com/

0개의 댓글