지난 시간에 따로 다루기로 했던 @Builder
어노테이션에 대해서 좀 더 자세히 알아보겠습니다.
빌더 패턴에 대한 내용이 들어있기 때문에 사전에 빌더 패턴이 학습된 상태여야 이해할 수 있습니다. 빌더 패턴에 대해서는 이 포스트를 참조해주세요.
@Builder
는 클래스에 빌더 패턴을 간단하게 적용시켜주는 어노테이션입니다. 빌더 패턴이 좋은 패턴이긴 하지만 빌더라는 별개의 클래스가 작성되어야하는 만큼 코드량이 늘어나기 때문에 이를 보조하기 위해 등장한 어노테이션이라고 할 수 있습니다.
다음은 Member 클래스와 빌더 클래스 코드입니다.
@Getter
@ToString
@NoArgsConstructor
public class Member {
//필수 필드
private String name;
//선택 필드
private int age;
//빌더 클래스
public static class MemberBuilder {
//필수 필드
private String name;
//선택 필드
private int age;
public MemberBuilder(String name) {
this.name = name;
}
public MemberBuilder age(int age) {
this.age = age;
return this;
}
public Member build() {
return new Member(name, age);
}
}
}
public static void main(String[] args) {
Member member = new MemberBuilder("알렉스").age(20).build();
System.out.println(member.toString());
}
@Builder
는 클래스에 적용하는 방식과 생성자에 적용하는 방식 두 가지 방식으로 나뉘어질 수 있습니다.
위 코드의 클래스 레벨에 @Builder
를 적용해보겠습니다.
@Getter
@ToString
@NoArgsConstructor
@Builder
public class Member {
//필수 필드
private String name;
//선택 필드
private int age;
}
이게 끝입니다. 컴파일 타임에 @Builder
가 알아서 빌더 클래스를 만들어서 빌더에 대한 작업을 수행하게 됩니다.
하지만, 이 방식은 컴파일과정에서 에러가 발생합니다.
이 이유는 클래스 레벨에 @Builder
를 사용하게 되면 클래스에 존재하는 모든 필드에 대한 생성자가 필요한데, 우리는 @NoArgsConstructor
를 사용해서 빈 생성자를 생성했기 때문에 에러가 발생하는 것 입니다.
그렇기 때문에 @AllArgsConstructor
를 사용해서 모든 필드를 갖는 생성자를 만들어주도록 합니다.
이때 아무 위치에서나 생성자를 통해 객체를 생성할 수 없도록
@AllArgsConstructor
의AccessLevel
을PRIVATE
나PROTECTED
로 설정해줍니다.
@Getter
@ToString
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Builder
public class Member {
//필수 필드
private String name;
//선택 필드
private int age;
}
public class Main {
public static void main(String[] args) {
Member member = Member.builder()
.name("알렉스")
.age(20)
.build();
System.out.println(member.toString());
}
}
하지만 주석이 보이시나요? 모든 필드를 받으면 안됩니다. 우리는 필수 필드와 선택 필드가 구분되어있기 때문입니다. 원하는 필드 값만 입력받고 싶다면 우리는 생성자 레벨에서 @Builder
를 사용해야합니다.
원하는 값만 빌더 패턴으로 입력받고 싶다면, 특정 필드가 들어있는 생성자를 작성하고 @Builder
를 붙이기만 하면 됩니다.
@Getter
@ToString
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Member {
//필수 필드
private String name;
//선택 필드
private int age;
@Builder
public Member(String name) {
this.name = name;
}
}
위와 같이 작성하게 되면 builder로는 name 필드만을 입력받을 수 있게 됩니다.
age
를 입력받으려고하면 다음과 같이 에러가 발생하게 됩니다.이렇게 하면 필수가 아닌 필드는 외부에 노출되지 않고, 필요한 값에만 빌더 패턴을 적용시켜서 값을 받을 수 있게 됩니다.
위 방식대로 진행하면 필수인 필드에 대해서만 빌더 패턴을 적용시켜서 값을 받을 수 있게 됩니다. 그러나 필수 필드임에도 빈 문자열을 전달하게되면 의도와는 다르게 정상적으로 객체가 생성됩니다.
@Getter
@ToString
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Member {
//필수 필드
private String name;
//선택 필드
private int age;
@Builder
public Member(String name) {
this.name = name;
}
}
public class Main {
public static void main(String[] args) {
Member member = Member.builder().build();
System.out.println(member.toString());
}
}
이러한 현상을 방지하고 싶다면 builderMethodName
을 이용해서 필수 필드를 명확하게 지정할 수 있게 됩니다.
@Getter
@ToString
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Member {
//필수 필드
private String name;
//선택 필드
private int age;
@Builder(builderMethodName = "cons")
private Member(String name, int age) {
this.name = name;
this.age = age;
}
@Builder(builderMethodName = "onlyNameBuilder")
public static Member onlyNameBuilder(String name) {
return Member.cons().name(name).build();
}
}
public class Main {
public static void main(String[] args) {
Member member = Member.onlyNameBuilder("알렉스");
System.out.println(member.toString());
}
}
이 방식 외에도 필수 필드에
@NonNull
을 붙여줌으로써 필수 필드임을 알릴 수 있습니다.
@NonNull
은 어노테이션이 붙은 변수에 null이 들어오면NullPointerException
을 발생시킵니다.@NonNull private String name;