Java | Android

롬복(Lombok)의 어노테이션들 - (val/var, NonNull, Cleanup, ToString)

partner_jun 2017. 5. 30. 23:25

개인적으로 현 시점 자바 프로그래밍에서 롬복(Lombok)은 빼놓을 수 없는 라이브러리라고 생각한다. 많은 사람들이 JAVA를 싫어하는 이유인 보일러 플레이트 코드(Boilerplate code : 반복적으로 사용되는 코드) 대부분을 어노테이션 추가만으로 깔끔하게 해결해주기 때문이다. 그만큼 롬복은 자주 사용하고 있지만 생각보다 모르고 있던 어노테이션도 많고, 새로운 어노테이션도 생겼기에 정리하고자 한다. 


롬복은 프로젝트 폴더에 lombok.config 파일을 만들어 세부적인 설정을 적용할 수 있다. 기본적으로 disable로 설정되어있는 어노테이션은 flagUsage를 ALLOW로 설정해야 사용이 가능해진다.

lombok.---.flagUsage = ALLOW

---는 어노테이션명




1. val

정말 굉장한 어노테이션이다. 스칼라의 val 키워드와 마찬가지로 객체의 타입을 추론한 불변 값을 선언한다. 하지만 스칼라와 달리 지역 변수와 foreach 구문에만 사용할 수 있다(메소드 파라미터, 클래스의 필드에는 사용할 수 없다)

val str = "Hello!"; // final String str = "Hello!"와 같다.
System.out.println(str); // Hello!

val lst = new ArrayList<String>(); // 다이아몬드 연산자(<>) 안에 타입을 넣지 않으면
// ArrayList<Object> 로 선언된다.
lst.add("Hello");
lst.add("World!");
System.out.println(lst); // [Hello, World!]

val value = 1000;
System.out.println(value); // 1000

val value2 = value * value;
System.out.println(value2); // 1000000


변수를 선언하는 var 키워드도 롬복에 구현되어 있지만 아직 Stable이 아닌 Experimental 기능이고, disable으로 설정되어 있다. 때문에 var 어노테이션을 사용하려면 lombok.config 파일에서 

lombok.var.flagUsage = ALLOW

위와 같은 설정이 필요하다.




2. @NonNull

안드로이드 개발을 경험했다면 친숙한 어노테이션이다. 메소드의 파라미터에 사용되고, null이 파라미터로 전달되면 예외을 던진다.

public static void printSomething1(String str) {
System.out.println(str);
}

public static void printSomething2(@NonNull String str) {
System.out.println(str);
}


public static void main(String[] args) {

printSomething1(null); // null

printSomething2(null); // Exception in thread "main" java.lang.NullPointerException: str

}

던져지는 예외는 기본적으로 NullPointerException이지만

lombok.nonNull.exceptionType = IllegalArgumentException

설정을 통해 던져지는 예외를 IllegalArgumentException으로도 설정할 수 있다. 이렇게 설정하면 IllegalArgumentException으로 catch할 수 있다.




3. @Cleanup

try-with-resource 구문과 비슷한 효과를 가진다. 구문이 종료될 때 AutoCloseable 인터페이스의 close()가 호출되는 try-with-resource와 달리 Scope가 종료될 때 close()가 호출된다.

// try-with-resource 구문.
// try 구문이 종료될때 scanner.close()가 호출됨.
try(val scanner = new Scanner(System.in)) {
val value = scanner.nextLine();
System.out.println(value);
}
/**
* 이 메소드가 종료될 때 scanner.close()가 호출된다.
*/
public static void getAndPrint() {
@Cleanup val scanner = new Scanner(System.in);
val value = scanner.nextLine();
System.out.println(value);
}




4. @Setter / @Getter

자바 POJO 형식에 맞게 필드의 Setter나 Getter를 만든다. 클래스에 선언하면 필드 모두에 적용되고, 어노테이션의 파라미터로 AccessLevel을 이용해 Setter / Getter의 접근제한자를 설정할 수 있다.

@Setter
@Getter
public class LombokClass1 {
private String name = "lombokClass1";
}
public class LombokClass2 {
@Setter(AccessLevel.PRIVATE)
@Getter
private String name = "lombokClass2";
}
val lombokClass1 = new LombokClass1();
lombokClass1.setName("lc1");
System.out.println(lombokClass1.getName()); // lc1

val lombokClass2 = new LombokClass2();
// lombokClass2.setName("lc2"); // private setter
System.out.println(lombokClass2.getName()); // lombokClass2


private final로 선언된 필드는 @Getter의 파라미터 중 lazy를 true로 설정할 수 있는데, lazy를 true로 설정할 경우 해당 필드의 getter가 호출 될 때 필드의 값을 설정한다.

public class LombokClass1 {
@Getter
private final String name = defaultName();

private String defaultName(){
System.out.println("LombokClass1 - defaultName() ");
return "Hello";
}
}


public class LombokClass2 {
@Getter(lazy = true)
private final String name = defaultName();

private String defaultName(){
System.out.println("LombokClass2 - defaultName() ");
return "Hello";
}
}

val lombokClass1 = new LombokClass1(); // LombokClass1 - defaultName()

val lombokClass2 = new LombokClass2(); // 출력 없음




5. @ToString

클래스의 toString() 메소드를 오버라이드한다. @ToString 어노테이션의 파라미터 exclude로 출력하지 않을 필드명을 입력할 수 있고, includeFieldNames로 필드명을 생략할지 포함할지 여부, callSuper로 상위 클래스의 toString()을 호출할지 여부를 설정할 수 있다.

@ToString(exclude = {"value", "value2"})
public class LombokClass1 {
private String name = "lombokClass1";
private int value = 1234;
private int value2 = 5678;
}

@ToString(includeFieldNames = false)
public class LombokClass2 {
private String name = "lombokClass2";
private int value = 9876;
}

@ToString(callSuper = true)
public class LombokClass3 extends LombokClass1 {
private String name2 = "lombokClass3";
}
val lombokClass1 = new LombokClass1();
System.out.println(lombokClass1); // LombokClass1(name=lombokClass1)

val lombokClass2 = new LombokClass2();
System.out.println(lombokClass2); // LombokClass2(lombokClass2, 9876)

val lombokClass3 = new LombokClass3();
System.out.println(lombokClass3); // LombokClass3(super=LombokTest.LombokClass1(name=lombokClass1), name2=lombokClass3)