오늘은 예전에 lombok을 잘써보자! 시리즈에서 조금 추가된 내용을 써보려고 한다. lombok 버전도 올라가면서 새로 추가된 어노테이션도 있고 놓쳤던 부분도 있을 수 있어 좀 더 추가하여 내용을 이어가보자. 참고로 지금 필자가 lombok 을 사용하는 버전은 1.18.2 버전이다. 지금 현재까지 나온 버전은 1.18.4 버전으로 알고 있다.

lombok을 잘써보자! (1)

lombok을 잘써보자! (2)

@Value

Value 어노테이션이다. 이것은 불변을 의미한다.  아주 간단하게 클래스 레벨에 @Value 어노테이션만 선언하면 사용할 수 있다.  코드를 보면서 살펴보도록 하자.

@Value
public class ValueExample {
    String name;
    String email;
}

기본적으로 위와 같이 선언했을 경우 필드는 기본적으로 private 접근제어자와 final 이 붙은 상수가 된다. final이 붙어 setter는 존재하지 않고 getter만 존재한다. 클래스 자체도 final class로 상속을 받을 수 없다.  @Data 어노테이션과 비슷하게 equals, hashCode, toString을 함께 만들어 준다. @Data 어노테이션이 비슷하지만 불변인 정도? 그 정도로만 생각해도 문제없을 듯 하다.  기본 생성자는 private 생성자이다.  기본생성자는 만들어 주지만 private 생성자로 만들어 준다.  위의 클래스를 바닐라 자바로 본다면 다음과 같을 것이다.

public final class ValueExample {
    private final String name;
    private final String email;

    public ValueExample(String name, String email) {
        this.name = name;
        this.email = email;
    }

    private ValueExample() {
        this.name = null;
        this.email = null;
    }

    public String getName() {
        return this.name;
    }

    public String getEmail() {
        return this.email;
    }
    // equals, hashCode, toString
}

위와 비슷한 모양으로 코드가 생성될 것으로 판단된다.  @Value어노테이션의 속성으로는 staticConstructor 가 존재하는데 static한 생성자를 생성해주는 속성이다. 이 속성을 사용할 경우에는 모든 생성자가 private 으로 되고 정의해둔 해당 static 메서드만 사용할 수 있다.

@Value(staticConstructor = "of")
public class ValueExample {
    String name;
    String email;
}

ValueExample ve = ValueExample.of("wonwoo", "wonwoo@test.com");

주로 DTO로 사용할 때 사용하면 될 듯 하다.

@Wither

이번에는 Wither 어노테이션이다. 음.. 이 어노테이션도 불변?과 관련이 있다. 해당 프로퍼티를 다시 어사인할때 해당 Object를 변경하는게 아니라 새로운 Object를 리턴해준다. 이 어노테이션은 필드 혹은 클래스에 붙일 수 있다.

public class WitherExample {
    @Wither
    private final String name;

    @Wither
    private final String email;

    public WitherExample(String name, String email) {
        this.name = name;
        this.email = email;
    }
}

위와 같이 필드를 정의 했을때 다음과 같이 사용할 수 있다.

WitherExample we = new WitherExample("wonwoo", "wonwoo@test.com");
WitherExample we1 = we.withName("woo");

위에서 말했다시피 해당 Object를 새로 만들어 return 해 주고 있다. 만약 위와 같이 모든 필드에 적용하고 싶다면 클래스 레벨에 @Wither 어노테이션을 붙어도 된다.

@Wither
public class WitherExample {

    private final String name;

    private final String email;

    public WitherExample(String name, String email) {
        this.name = name;
        this.email = email;
    }
}

위 아래 모두 동일한 코드가 생성된다. 위 코드를 바닐라 자바로 바꾸어 본다면 아래와 같을 것이다.

public class WitherExample {
    private final String name;
    private final String email;

    public WitherExample(String name, String email) {
        this.name = name;
        this.email = email;
    }

    public WitherExample withName(String name) {
        return this.name == name ? this : new WitherExample(name, this.email);
    }

    public WitherExample withEmail(String email) {
        return this.email == email ? this : new WitherExample(this.name, email);
    }
}

어렵지 않다.  @Wither 속성중에 해당 메서드의 접근제어자를 설정해 줄 수도 있다. 

public class WitherExample {

    @Wither(AccessLevel.PROTECTED)
    private final String name;
    //...
}

@Singular와 @Builder.Default

이 어노테이션은 @Builder 어노테이션을 사용할 때 유용하다.  @Builder 어노테이션은 다들 아시다시피 builder 패턴으로 해당 Object을 만들어주는 그런 어노테이션이다. 생성자의 파라미터가 많을 경우 유용한 어노테이션이다. 그 때 사용할 수 있는 어노테이션이 @Singular 어노테이션과 @Builder.Default 어노테이션이다. @Singular 어노테이션은 컬렉션에 사용할 수 있는데 하나의 어떤 Object을 컬렉션에 추가 할 수도 있고  컬렉션 모두를 추가할 수 도 있다.

@Builder.Default 어노테이션은 @Builder 어노테이션을 사용할 경우 미리 선언된 프로퍼티의 값을 사용할 수 없다. 

아래의 예제를 보면서 살펴보도록 하자.

@Builder
public class SingularExample {

    @Builder.Default
    private String name = "wonwoo";

    @Singular
    private List<String> phones;
}

위 처럼 사용할 경우에는 name에 기본적으로 wonwoo 라는 값을 넣어두었다. 만약 @Builder.Default 어노테이션이 존재 하지 않는다면 해당 값을 초기화 되지 않는다.

SingularExample singularExample  = SingularExample
    .builder()
    .build();

SingularExample(name=wonwoo, phones=[])

위와 같이 아무 값을 넣지 않았지만 name에는 wonwoo라는 값이 존재한다.  만약 @Builder.Default 를 제거한다면 아래와 같은 값이 출력 될 것이다.

@Builder
public class SingularExample {
    private String name = "wonwoo";

    @Singular
    private List<String> phones;
}

SingularExample(name=null, phones=[])

@Singular 어노테이션은 단일 Object를 컬렉션에 추가할 수도 있고 컬렉션 자체를 추가할 수 도 있는 어노테이션이다. 사용하는 코드를 살펴보자.

SingularExample singularExample = SingularExample
    .builder()
    .phone("010-0000-1111")
    .phone("010-0000-1112")
    .phones(Arrays.asList("010-1111-2222", "010-1111-2222"))
    .build();

위와 같이  phones 라는 컬렉션에 phone을 하나씩 하나씩 추가할 수 있다. 컬렉션을 사용할 때 유용한 어노테이션인 듯 싶다. 

빌더 어노테이션을 유용하게 사용한다면 한번씩 살펴보는 것도 나쁘지 않다.

@FieldNameConstants

이 어노테이션은 어노테이션명 그대로 필드명을 상수로 사용하는 어노테이션이다. 근데 조금 문서와 다르다. 필자는 intellij를 쓰는데 해당 플러그인이 지원을 제대로 해주지 않는 건지.. 모르겠지만 필자가 테스트 해본 걸로 글을 작성할 예정이니 참고하면 되겠다. 보니까 1.18.4 부터 디자인이 조금 바뀌었다고 한다. 일단 이런게 있다고만 알자!

@FieldNameConstants
public class FieldNameConstantsExample {
    private String abcd;
}

위와 같이 상수를 담고 있는 클래스에 작성하면 된다.  그럼 해당 필드명 그대로 상수 값이 된다. 

String fieldAbcd = FieldNameConstantsExample.FIELD_ABCD;
System.out.println(fieldAbcd);
// abcd

위와 같이 작성하고 출력할 경우에 abcd 라는 문자열이 출력 된다. 

@FieldNameConstants
public class FieldNameConstantsExample {

private String ABCD;
}

만약 위와 같이 상수를 ABCD 대문자로 하면 필드명에 _ 언더바가 많이 생긴다.

String fieldAbcd = FieldNameConstantsExample.FIELD_A_B_C_D;

또 한  해당 필드에 접근제어를 할 수 도 있다.

@FieldNameConstants(level = AccessLevel.PRIVATE)
private String abcd;

글쎄다. 사용할지는 모르겠다. 아마도 당분간은 사용하지 않을 것 같다.

@Accessors

해당 어노테이션은 클래스 레벨에 사용할 경우 setter를 체이닝하게 만들 수 있는 어노테이션이다.  참고로 필드에서 사용할 수 있는 prefix는 언급하지 않겠다. 딱히 사용할 일도 없을 것 같아서.. 그냥 필드명 그대로 사용할 것 같다.

어쨌든 제외 하고도 두개의 속성이 있는데 다른점은 메서드명이 달라진다는 것뿐이지 하는 역할을 같다.

@Accessors(chain = true)
@Data
public class AccessorsExample {

    private String name;
    private String email;
}

위와 같이 chain 옵션은 사용할 경우에는 setter가 만들어 질 때 해당 클래스를 다시 리턴하는 체이닝방식으로 만들어 진다.

AccessorsExample accessorsExample = new AccessorsExample();
AccessorsExample emailAccessors = accessorsExample.setEmail("wonwoo@test.com");

그렇다고 해서 불변은 아니다.  상태를 변경시킨후에 해당 Object을 리턴할 뿐이다. 

public AccessorsExample name(String name) {
    this.name = name;
    return this;
}

public AccessorsExample email(String email) {
   this.email = email;
   return this;
}

대략 위와 같이 setter가 만들어 진다고 생각하면 된다.
이번엔 fluent 속성을 사용해보자.

@Accessors(fluent = true)
@Data
public class AccessorsExample {

    private String name;
    private String email;
}

달라진 것 메서드명 뿐이다. getter, setter 모두 달라진다.

AccessorsExample accessorsExample = new AccessorsExample();
AccessorsExample emailAccessors = accessorsExample.email("wonwoo@test.com");
AccessorsExample nameAccessors = accessorsExample.name("wonwoo");
String name = accessorsExample.name();

get과 set이 라는 prefix가 사라지고 email, name 으로 상태를 변경 시키고 값을 가져올 수 있다.  이 또한 상태만 변경시키지 새로운 Object을 만드는 것은 아니다. 

오늘은 이렇게 lombok을 잘써보자! 3탄을 가져나왔다. 예전에 썼던 1탄, 2탄 모두 참고하면 좋겠다.

부디 유용한 글이 되었기를..