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
출처: https://javafactory.tistory.com/1571?category=470973 [FreeLife의 저장소]
6. @EqualsAndHashCode
equals(Object) 메소드와 hashCode() 메소드를 오버라이드한다. @ToString 어노테이션과 마찬가지로 exclude 파라미터로 필드를 제외하거나 callSuper 파라미터로 부모 객체를 생략하거나 포함할 수 있다. 또, 필드에 객체 직렬화(Serializable)에서 제외할때 사용하는 transient 키워드를 사용하면 exclude 파라미터로 입력한 것과 같이 두 메소드에서 제외된다.
@EqualsAndHashCode(exclude = "value")
private static class LombokClass1 {
private String name = "lombok";
@Setter
private int value;
}
@EqualsAndHashCode
private static class LombokClass2{
private String name = "lombok";
@Setter
private int value;
}
val l1 = new LombokClass1();
l1.setValue(100);
System.out.println(l1.hashCode()); // -1097163153
val l2 = new LombokClass1();
l2.setValue(500);
System.out.println(l2.hashCode()); // -1097163153
val result = l1.equals(l2);
System.out.println(result); // true
val l3 = new LombokClass2();
l3.setValue(100);
System.out.println(l3.hashCode()); // -308116487
val l4 = new LombokClass2();
l4.setValue(500);
System.out.println(l4.hashCode()); // -308116087
val result2 = l3.equals(l4);
System.out.println(result2); // false
lombok.config 파일에서
lombok.equalsAndHashCode.doNotUseGetters = false
설정을 통해 오버라이드된 equals(), hashCode() 메소드가 각 필드의 Getter를 사용할지 여부를 설정할 수 있다.
7. @NoArgsConstructor / @AllArgsConstructor / @RequiredArgsConstructor
클래스의 생성자를 만들어준다. 세 종류를 중복해서 사용할 수 있으며 어노테이션 이름 그대로 @NoArgsConstructor는 파라미터가 없는 생성자, @AllArgsConstructor는 모든 필드를 파라미터로 가지는 생성자를 만든다. @RequiredArgsConstrucotr는 기본 값이 없고 @NonNull 어노테이션이 붙은 필드를 파라미터로 입력받는 생성자를 만든다..
이 세 가지 생성자를 만드는 어노테이션 모두 객체를 만드는 static 메소드의 이름을 입력받는 staticName, 생성자의 접근제한자를 설정하는 access를 파라미터로 입력할 수 있다.
@NoArgsConstructor
@AllArgsConstructor(staticName = "of")
@RequiredArgsConstructor
@ToString
public static class LombokClass {
private String name = "lombok";
@NonNull private int value;
@NonNull private int value2;
}
val requiredArgs = new LombokClass(100, 200);
System.out.println(requiredArgs); // LombokClass(name=lombok, value=100, value2=200)
val noArgs = new LombokClass();
System.out.println(noArgs); // LombokClass(name=lombok, value=0, value2=0)
val allArgs1 = LombokClass.of("Hello", 500, 600);
val allArgs2 = new LombokClass("World", 900, 1000);
System.out.println(allArgs1); // LombokClass(name=Hello, value=500, value2=600)
System.out.println(allArgs2); // LombokClass(name=World, value=900, value2=1000)
또, 안드로이드에서는 lombok.config 파일에
lombok.anyConstructor.suppressConstructorProperties = true
설정이 되어있어야 정상적으로 컴파일된다. @java.beans.ConstructorProperties 어노테이션이 자동으로 추가되지 않게 하는 설정이다.
8. @Data
롬복에서 가장 중요한 어노테이션 중 하나다. 앞서 살펴본 @ToString, @EqualsAndHashCode, @Getter/@Setter, @RequiredArgsConstructor 어노테이션을 모두 사용한 것과 같은 효과를 준다. 하지만 세부 설정을 하려면 각각의 어노테이션을 붙여야 한다는 점, @RequiredArgsConstructor 어노테이션으로 만들어지는 생성자는 다른 생성자가 없을 때에만 만들어진다는 점을 주의해야 한다.
@AllArgsConstructor
@Data(staticConstructor = "of")
public static class LombokClass {
private String name = "lombok";
@NonNull private int value;
@NonNull private int value2;
}
// val lombokClass = LombokClass.of(100, 200); // of 메소드가 만들어지지 않음
// System.out.println(lombokClass);
val lombokClass1 = new LombokClass("Hello", 100, 200);
System.out.println(lombokClass1); // LombokClass(name=Hello, value=100, value2=200)
val lombokClass2 = new LombokClass("Hello", 100, 200);
val result = lombokClass1.equals(lombokClass2);
System.out.println(result); // true
9. @Value
@Data 어노테이션과 비슷하지만 필드를 변경할 수 없는 '불변 객체'가 만들어진다. 필드에 @Wither 어노테이션을 이용하면 with필드명(값) 메소드가 만들어지는데, 이 메소드를 이용하면 값을 변경한 새로운 객체를 만들어준다. 값을 변경하지 않는 프로그래밍에서 아주 유용하게 활용된다.
@Value
@ToString(exclude = "name")
public static class LombokClass {
@Wither(AccessLevel.PROTECTED)
private String name;
private int value;
private int value2;
}
val lombokClass = new LombokClass("Hello", 200, 500);
System.out.println(lombokClass); // LombokClass(value=200, value2=500)
val lombokClass2 = lombokClass.withName("World");
System.out.println(lombokClass2); // LombokClass(value=200, value2=500)
val result = lombokClass.equals(lombokClass2);
System.out.println(result); // false
@Value 어노테이션은 val 어노테이션을 사용하기 때문에 val 어노테이션이 사용가능한 상태에서만 @Value 어노테이션도 사용이 가능하다.
10. @Builder
빌더 패턴을 적용한 객체 생성 메소드/클래스를 만들어준다.
builderClassName 파라미터로 nested 빌더 클래스의 이름을 (클래스명Builder가 기본),
builderMethodName으로 빌더 클래스를 반환하는 static 메소드의 이름을 (builder()가 기본),
buildMethodName으로 객체를 반환하는 빌드 메소드의 이름 (build()가 기본)을 설정할 수 있다.
필드에 @Builder.Default 어노테이션을 붙여 기본 값을 설정할 수 있고,
@Singular 어노테이션을 붙여 빈 collection을 자동으로 만들 수 있다. @Singluar 어노테이션은 파라미터로 builder에서 값을 추가할 때 사용되는 메소드의 이름을 입력받는다. 또, @Singular 어노테이션으로 만들어진 collection은 수정할 수 없다.
@Builder
@ToString
public static class LombokClass {
private String name;
private int value;
@Builder.Default private int value2 = 999; // 기본 값 설정
@Singular("addVal") private List<Integer> values;
}
val lombokClass = LombokClass.builder()
.name("Hello")
.value(500)
.addVal(100)
.addVal(200)
.addVal(300)
.build();
System.out.println(lombokClass); // LombokClass(name=Hello, value=500, value2=999, values=[100, 200, 300])
val val0 = lombokClass.values.get(0);
System.out.println(val0); // 100
// lombokClass.values.clear(); // Exception in thread "main" java.lang.UnsupportedOperationException
// System.out.println(lombokClass);
출처: https://javafactory.tistory.com/1572?category=470973 [FreeLife의 저장소]
11. @SneakyThrows
논란의 여지가 있는 어노테이션이라고 소개되고 있다. 메소드 선언부에 사용되는 throws 키워드 대신 사용하는 어노테이션으로 예외 클래스를 파라미터로 입력받는다.
public static String utf8ToString(byte[] bytes) throws UnsupportedEncodingException{
return new String(bytes, "UTF-8");
}
@SneakyThrows(UnsupportedEncodingException.class)
public static String utf8ToStringWithLombok(byte[] bytes) {
return new String(bytes, "UTF-8");
}
val bytes = "Hello!".getBytes();
try {
val result1 = utf8ToString(bytes);
System.out.println(result1);
} catch(UnsupportedEncodingException e){
e.printStackTrace();
}
val result2 = utf8ToStringWithLombok(bytes);
System.out.println(result2);
예외 처리라는 원칙을 무시하는 이상한 어노테이션이라고 생각될 수 있지만, 몇 가지 특별한 경우에 아주 유용하게 사용된다. 아래는 롬복 홈페이지에서 소개하고 있는 '특별한' 경우다.
1) Runnable같은 인터페이스 Runnable의 run() 메소드 안에서 발생한 예외는 호출한 쓰레드로 제대로 전파되지 않는다. 모든 예외가 RuntimeException으로 묶여 던져지기 때문에(심지어 예외 메시지가 비어있는 경우도 있다) 정상적인 예외 처리를 할 수 없는데, 그런 경우에 유용하게 사용된다. 2) '발생할 수 없는' 예외 위 예제에서 new String(byteArr, "UTF-8")은 지원되지 않는 인코딩 타입에 대한 예외인 UnsupportedEncodingException이 던져질 수 있다고 선언되어 있다. 하지만 JVM 스펙에서는 UTF-8이 항상 사용 가능해야 하기 때문에 이 예외는 '발생할 수 없는' 예외다.
|
사실 @SneakyThrows 어노테이션을 사용한 메소드에서 예외가 발생하면 catch문에서 e.printStackTrace(); 메소드를 호출한 것과 같이 예외가 출력된다.
@SneakyThrows(UnsupportedEncodingException.class)
public static String utf8ToStringWithLombok(byte[] bytes) {
return new String(bytes, "UTF-88"); // UTF-88은 없는 인코딩 타입이므로 예외가 발생함
}
val bytes = "Hello!".getBytes();
val result = utf8ToStringWithLombok(bytes);
// Exception in thread "main" java.io.UnsupportedEncodingException: UTF-88
System.out.println(result);
예외에 대한 특별한 처리(catch문에서 값을 복구한다던지, 다시 시도하는)를 구현하지 않을 것이라면 위의 '특별한' 경우 외에도 아주 유용하다.
12. @Synchronized
메소드에 사용되는 어노테이션으로 기본적으로 지원되는 synchronized 키워드보다 더 세세한 설정이 가능한 어노테이션이다. synchronized 키워드는 static 혹은 instance 단위로 락을 걸지만 @Synchronized 어노테이션은 파라미터로 입력받는 Object 단위로 락을 건다. 파라미터로 아무 것도 입력하지 않으면 어노테이션이 사용된 메소드 단위로 락을 건다.
public static class LombokClass {
private static final Object COUNT_LOCK = new Object();
private String name = "lombok";
private int count = 0;
@Synchronized("COUNT_LOCK")
public int addCount(int c) {
count += c;
return count;
}
@Synchronized("COUNT_LOCK")
public int getCount(){
return count;
}
@Synchronized
public String getName(){
return name;
}
}
13. @Log
이 어노테이션도 아주 유용하게 사용된다. 클래스 상단에 항상 선언하는 static final log 필드를 자동으로 생성해준다. 지원되는 Logger에 따라 다른 어노테이션이 사용된다.
공식 홈페이지에서 소개하고 있는 Logger별 어노테이션
이미 Logger 생성 구문을 템플릿으로 만들었다 하더라도, 롬복을 사용한다면 이 어노테이션을 사용하는 것을 고려해볼만 하다.
@Log
public static class LombokClass {
private String name = "lombok";
public String getName(){
log.warning("call getName()");
return name;
}
}
val lombokClass = new LombokClass();
val name = lombokClass.getName(); // 5월 31, 2017 2:21:26 오후
// 경고: call getName()
System.out.println(name);
Stable 기능으로 소개되고 있는 어노테이션들을 알아보았다. 이 어노테이션들 외에도 현재 Experimental에 분류된 어노테이션에는 스칼라의 Implicit 클래스와 비슷하게 사용할 수 있는 @ExtensionMethod, static method만을 사용하는 Utility 클래스에 사용하는 @UtilityClass 등 유용하고 놀라운 어노테이션들이 많다. 언제 Stable한 기능으로 추가될지는 모르지만 한번쯤 살펴보고 사용해보는 것도 좋을 것 같다.
출처: https://javafactory.tistory.com/1573?category=470973 [FreeLife의 저장소]
'Language > Java' 카테고리의 다른 글
자바 스트림 연산 (0) | 2019.07.30 |
---|---|
커스텀 어노테이션 (0) | 2019.07.30 |
jedis-자바에서 레디스 (0) | 2019.07.05 |
자바 폴더생성 (0) | 2019.07.02 |
타이머 쓰레드 (0) | 2019.05.01 |