Java | Android

롬복(Lombok)의 어노테이션들 - (SneakyThrows, Synchronized, Log)

partner_jun 2017. 5. 31. 13:23

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같은 인터페이스

Runnablerun() 메소드 안에서 발생한 예외는 호출한 쓰레드로 제대로 전파되지 않는다. 모든 예외가 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한 기능으로 추가될지는 모르지만 한번쯤 살펴보고 사용해보는 것도 좋을 것 같다.