java 개발자에 있어 lombok은 아주 좋은 라이브러리이다. 어노테이션 하나로 자동으로 바이트코드를 만들어주니 더 할 것이 없는 라이브러리이다. 다른 언어들은 언어 자체에서 지원해주긴 하지만..
필자도 아주아주 잘 쓰지는 못하지만 필자가 아는 것만큼 포스팅을 해보자!

@Data

lombok을 사용한다면 제일 많이 사용하는 어노테이션이다. 이 어노테이션은 다재다능한 기능이다. 사용하는 사람은 알겠지만 getter, setter, toString, hasCode, equals, constructor 등 많은 부분을 자동으로 생성해준다.
각각 부분적으로는 밑에서 설명하도록 하겠다.
@Data 어노테이션에는 속성이 한개 있는데 staticConstructor 라는 속성이다. 말그대로 static한 생성자? 를 만들어 주는 것이다.

@Data(staticConstructor = "of")
public class DataObject {
  private final Long id;
  private String name;
}

위와 같이 선언한다면 다음과 같이 사용가능하다.

DataObject dataObject = DataObject.of(1L);

id 경우에는 final이라 필수 생성자에 포함되어 있다. 만일 위와 같이 사용한다면 new로 생성할 수 없다.

DataObject dataObject2 = new DataObject(); // compile error

위와 같이 new 를 이용해 생성할 시에는 컴파일 에러가 발생한다.

위의 여러 메서드를 제외하고 한개의 메서드가 더 생성이 되는데 그 메서드는 canEqual 이라는 메서드이다. 해당 메서드의 역할은 instanceof로 타입정도만 체크 한다. 하지만 메서드의 접근제한자는 protected 이다. 필자도 사용할 일이 없었다.

XXXXArgsConstructor

위의 어노테이션은 생성자를 생성해주는 어노테이션이다. 생성자를 생성해주는 어노테이션은 3가지가 있다. 첫 번째는 디폴트 생성자를 생성해주는 @NoArgsConstructor 두 번째는 모든 필드의 생성자를 생성해주는 @AllArgsConstructor 마지막으로 필수 생성자를 생성해주는 @RequiredArgsConstructor 가 있다.
속성에는 여러가지 있는데 자주 사용할 법한 속성들만 설명하겠다. (필자가 아는 것 만..)
1. staticName : 위에서 @Data 어노테이션의 staticConstructor 와 동일하다. static한 생성자를 만들어 준다.
2. access : 접근제한을 할 수 있다. PUBLIC, MODULE, PROTECTED, PACKAGE, PRIVATE 등으로 설정가능 하다.
3. onConstructor : 생성자에 어노테이션을 작성할 수 있다.
공통적인 속성으로는 위와 같이 3가지가 존재한다.

@RequiredArgsConstructor(staticName = "of", onConstructor = @__(@Inject))
public class ConstructorObject {
  private final Long id;
  private final String name;
}

만약 위와 같은 어노테이션을 작성할 경우에는 다음과 같은 코드가 나올 것이라고 예상해본다.

class ConstructorObjectNot {
  private final Long id;
  private final String name;

  @Inject
  private ConstructorObjectNot(Long id, String name) {
    this.id = id;
    this.name = name;
  }
  public static ConstructorObjectNot of(Long id, String name) {
    return new ConstructorObjectNot(id, name);
  }
}

staticName가 존재해 access는 별루 의미가 없어 사용하지 않았다.

@Getter와 @Setter

어노테이션 이름 그대로 getter와 setter를 생성해준다. 클래스 레벨에도 사용가능하며 필드 레벨에도 사용가능하다.
공통 속성으로는 value, onMethod 속성이 존재한다. value의 경우에는 접근 제한을 할 수 있으며 onMethod 메서드의 어노테이션을 작성할 수 있다.

public class GetSetObject {

  @Getter(value = AccessLevel.PACKAGE, onMethod = @__({@NonNull, @Id}))
  private Long id;
}

만약 위와 같은 코드를 작성하였을 경우에는 다음과 같은 코드가 작성 될 것이다.

class GetSetObjectOnMethod {
  private Long id;

  @Id
  @NonNull
  Long getId() {
    return id;
  }
}

물론 @Setter 어노테이션에도 onMethod를 사용할 수 있다.

@Setter(onMethod = @__({@NotNull}))

그리고 @Getter, @Setter 각각이 다른 속성들을 한개씩 가지고 있는데. @Getter인 경우에는 lazy 속성이고 @Setter의 경우에는 onParam 이라는 속성이다.
@Getter 의 lazy 속성은 속성명 그대로 필드의 값은 지연시킨다는 것이다.

@Getter(value = AccessLevel.PUBLIC, lazy = true)
private final String name = expensive();

private String expensive() {
  return "wonwoo";
}

lazy가 true일때는 무조건 final 필드어야만 한다. lazy 속성이 false 일 경우에는 객체를 생성할 때 expensive() 메서드를 호출하지만 속성이 true일 경우에는 getName() 메서드를 호출할 때 expensive() 메서드를 호출 한다.

다음은 @Setter의 onParam 속성이다. 이 속성은 파라미터의 어노테이션을 작성할 수 있는 속성이다.

@Setter(onParam = @__(@NotNull))
private Long id;

만약 다음과 같은 코드가 있을 경우에는 아래와 같은 코드가 작성될 것이라고 판단된다.

class GetSetObjectOnParam {
  private Long id;

  public void setId(@NotNull Long id) {
    this.id = id;
  }
}

아주 간단한 코드이다. (물론 만든 사람은 아니겠지만..)

IDEA에서는 onParamonMethod에 @Column 어노테이션의 속성을 넣으면 잘 동작하지 않는다. onParam 은 파라미터에 적용되니 @Column 자체가 들어 갈 수 없으니 그렇다 쳐도 onMethod는 왜안되는지.. 플러그인 문제인듯 싶다.

@EqualsAndHashCode 와 @ToString

@EqualsAndHashCode 어노테이션은 이름 그대로 hashcode와 equals를 생성해주는 어노테이션이고, @ToString도 마찬가지로 toString() 메서드를 생성해주는 어노테이션이다.
공통 속성으로는 4가지 있는데 exclude, of, callSuper, doNotUseGetters가 존재 한다. exclude는 제외시킬 변수명을 작성하면 되고 of는 포함시킬 변수명을 작성하면 된다. callSuper 속성은 상위 클래스의 호출 여부를 묻는 속성이다. 마지막으로 doNotUseGetters의 속성은 getter 사용여부 인듯 하나 제대로 동작하지는 모르겠다.

@EqualsAndHashCode(of = "id")
@ToString(exclude = "name")
public class HashCodeAndEqualsObject {
  private Long id;
  private String name;
}

만일 위와 같이 작성하였다면 hasCode, equals, toString 모두 id만 존재하게 된다.
각각의 속성으로는 @EqualsAndHashCode 는 onParam, @ToString 는 includeFieldNames 속성이 존재한다. onParam 은 equals에 작성되며 위의 onParam 속성과 동일하므로 생략한다. includeFieldNames는 toString의 필드 명을 출력할지 하지 않을지의 여부이다. 만일 위의 코드로 includeFieldNames을 false로 한다면 다음과 같이 출력 된다.

HashCodeAndEqualsObject(null)

참고로 canEqual 메서드도 @EqualsAndHashCode 메서드에 포함되어 있다.

@val 와 @var

스칼라, 코틀린 이외에 다른 언어들의 키워드와 동일하게 타입추론을 한다.

public class ValAndVarTests {
  @Test
  public void valVarTest() {
    val arrVal = Arrays.asList(1, 2, 3, 4, 5);
    arrVal = new ArrayList<>(); // compile error

    var arrVar = Arrays.asList(1, 2, 3, 4, 5);
    arrVar = new ArrayList<>();
  }
}

val 경우에는 final 키워드가 생성된다. 그래서 다시 어사인을 할 경우에 컴파일 에러가 발생한다. 마찬가지로 var는 final이 존재 하지 않으므로 다시 어사인이 가능하다. 위의 코드를 다시 만들어 보면 다음과 같을 것으로 예상된다.

final List<Integer> arrVal1 = Arrays.asList(1, 2, 3, 4, 5);
arrVal1 = new ArrayList<>();

List<Integer> arrVar1 = Arrays.asList(1, 2, 3, 4, 5);
arrVar1 = new ArrayList<>();

위와 동일한 바이트코드가 나올 것으로 예상해본다.

@UtilityClass

유틸리티 클래스에 적용하면 되는 어노테이션이다. 만약 이 어노테이션을 작성하면 기본생성자가 private 생성되며 만약 리플렉션 혹은 내부에서 생성자를 호출할 경우에는 UnsupportedOperationException이 발생한다.

@UtilityClass
public class UtilityClassObject {
  public static String name() {
    return "wonwoo;";
  }
}

만약 위의 코드를 다시 작성해보면 다음과 같다.

class UtilityClassObjectNot {
  private UtilityClassObjectNot() {
    throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
  }
  public static String name() {
    return "wonwoo;";
  }
}

이번시간에는 이정도로 마무리를 짓도록 하자. 오늘은 자주 사용하는 lombok에 대해서 알아봤다. 다음에는 자주 사용은 하지 않지만 특이하거나 재미있는 코드를 살펴보도록 하자. (물론 자주 사용하는 것도 있다.)

오늘은 여기까지!