개발관련 포스팅을 보면 가끔 코드에 Annotation이 붙어있는걸 볼 수 있는데 뭔가 고수의 향기가 나는 것 같은 느낌을 받는다ㅎ
자주 사용하는 Annotation들은 Res Contraint을 걸어주는 타입을 주로 사용한다.
Custom을 시작하기 전, 몇가지 Annotation을 알아보자.
@Deprecated("Error Logic")
fun function1(a: Int, b: Int) : Int {
return a + b
}
Android API 버전에 따라 취소선이 나오는 메서드들이 있다. 바로 Deprecated Annotation을 사용하면 개인이 직접 타 개발자에게 알려줄 수 있는 기능이다. 주로 라이브러리나 SDK를 개발할 때 버전업에 따라 사용할 수 있을 것 같다.
Annotation안에는 설명을 해주는 commit같은 JavaDoc 기능도 포함되어있다. 커서를 가져다 대면 메세지가 나온다.
또한 Deprecated되었으니 다른 메서드를 추천하는 기능 또한 제공한다.
@Deprecated("Error Logic", ReplaceWith("functionNew1(a, b)"))
fun function1(a: Int, b: Int) : Int {
return a + b
}
fun function2(@IntRange(from = 0, to = 10) a: Int, b: Int) : Int {
return a + b + 1
}
fun function2(@FloatRange(from = 0.0, to = 10.0) a: Float, b: Float) : Float {
return a + b
}
파라미터의 범위 제약을 걸어주는 Annotation이다.
@StringRes와 같이 코드의 제약이 어느정도 강제되면 좋은 코드라고 볼 수 있다. 이유는 개발단계에서 버그 발생 커버리지를 높일 수 있기 때문이다.
위 메서드의 파라미터에 from..to 범위를 벗어난 값을 넣게 되면 컴파일 단계에서 알려주게 된다.
@JvmOverloads
fun function3 (a: Int = 0, b: Int = 0) : Int {
return a + b
}
코틀린과 자바를 한 프로젝트에서 같이 사용할 때 유용할 수 있다.
Kotlin에서는 파라미터의 Default Value를 제공하는데 자바에서 Default Value가 들어있는 kotlin 메서드를 호출하려하면 무조건 파라미터를 넣어야하는 불편함이 생긴다.
JvmOverloads Annotation은 자바에서 이를 구현할 수 있게 도와준다. 디컴파일된 .java파일로 차이를 살펴보자.
fun function3 (a: Int = 0, b: Int = 0) : Int {
return a + b
}
@JvmOverloads
fun function4 (a: Int = 0, b: Int = 0) : Int {
return a + b
}
/* Convert to .java */
/**
@JvmOverloads 미 적용
*/
public final int function3(int a, int b) {
return a + b;
}
// $FF: synthetic method
public static int function3$default(AnnotationType var0, int var1, int var2, int var3, Object var4) {
if ((var3 & 1) != 0) {
var1 = 0;
}
if ((var3 & 2) != 0) {
var2 = 0;
}
return var0.function3(var1, var2);
}
/**
@JvmOverloads 적용
*/
@JvmOverloads
public final int function4(int a, int b) {
return a + b;
}
// $FF: synthetic method
public static int function4$default(AnnotationType var0, int var1, int var2, int var3, Object var4) {
if ((var3 & 1) != 0) {
var1 = 0;
}
if ((var3 & 2) != 0) {
var2 = 0;
}
return var0.function4(var1, var2);
}
@JvmOverloads
public final int function4(int a) {
return function4$default(this, a, 0, 2, (Object)null);
}
@JvmOverloads
public final int function4() {
return function4$default(this, 0, 0, 3, (Object)null);
}
@JvmOverloads를 붙이지 않은 function3의 경우 추가로 생긴 $default 메서드 말고는 Overload된 함수가 없다.
하지만 funtion4의 경우 총 3개의 Overload 메서드를 볼 수 있다. 이를 통해 자바에서도 디폴트 값을 가지는 코틀린 메서드를 호출 할 수 있게 된다.
다만 아쉬운 점은 코틀린은 파라미터를 선택해서 넣어줄 수 있는데, 자바에서는 빌드된 파일처럼 int b값만을 줄 순 없나 보다. 코틀린 짱
이제 직접 Annotation을 만들어보자.
전에 SDK를 만들 때 회사 내부에서만 사용할 다이얼로그를 만들었던 적이 있다.
AlertDialog중 Neutral Button은 자주 사용하진 않지만, 사용하지 않는다고 해서 막아버리면 추후 필요할 때 SDK를 변경하거나 따로 또 Dialog를 만들어야하니 일단 만들어두고 visibility로 컨트롤 할 예정이다.
여기서 View.VISIBILITY 필드에 단순 Int값이 오는것을 방지하기 위해 Custom Annotation을 달아 보았다.
@Target(AnnotationTarget.FIELD)
@IntDef(View.GONE, View.VISIBLE, View.INVISIBLE)
@Retention(AnnotationRetention.SOURCE)
annotation class Visibility
위와 같이 코드를 입력하면 @Visibility를 사용할 수 있게된다.
public enum class AnnotationTarget {
/** Class, interface or object, annotation class is also included */
CLASS,
/** Annotation class only */
ANNOTATION_CLASS,
/** Generic type parameter */
TYPE_PARAMETER,
/** Property */
PROPERTY,
/** Field, including property's backing field */
FIELD,
/** Local variable */
LOCAL_VARIABLE,
/** Value parameter of a function or a constructor */
VALUE_PARAMETER,
/** Constructor only (primary or secondary) */
CONSTRUCTOR,
/** Function (constructors are not included) */
FUNCTION,
/** Property getter only */
PROPERTY_GETTER,
/** Property setter only */
PROPERTY_SETTER,
/** Type usage */
TYPE,
/** Any expression */
EXPRESSION,
/** File */
FILE,
/** Type alias */
@SinceKotlin("1.1")
TYPEALIAS
}
리플렉션(Reflection)이란 런타임 때 프로그램의 구조(객체, 함수, 프로퍼티)를 분석해 내는 기법을 말한다. startActivity때 자주 사용하던 Activity::class.java또한 리플렉션에 포함된다. 리플렉션을 통해 클래스의 상태(final, sealed)등 다양한 요소들을 파악할 수 있다.
RUNTIME: compile time 과 binary 에도 포함되고, reflection 을 통해 접근이 가능하다. Custom Annotation 에 @Retention 을 표시해주지 않을경우, 디폴트로 값이기도 하다.
이렇게 설명을 마무리하고, 위 @Visibility를 적용한 Data Class를 보자
data class AlertDialogAttr(
@FloatRange(from = 0.0, to = 48.0)
val textSize: Float = ALERT_DIALOG_TITLE_DEFAULT_SIZE,
@FontRes
var fontId: Int = R.font.alert_dialog_title_default,
@StringRes
val textId: Int = R.string.alert_dialog_title_default,
@ColorRes
var colorId: Int = R.color.alert_dialog_title_default,
@Visibility
var visibility: Int = View.INVISIBLE
)
Custom외에도 다양한 Annotation으로 제약을 걸었다. 1~4번째는 위에서 설명했으니 과연 Custom한 것도 잘 동작할까?
정말로 단순 Int값을 넣으니 에러가 발생했다. 또한 설명까지 친절하게 알려준다.
여기서 더 나아가 만일 View.GONE의 Int값을 직접 입력해도 에러가 발생할까?
이 또한 컴파일 에러가 발생한다. 오로지 정의한 3가지의 요소만이 들어갈 수 있게 되어 버그 방지와 Dos로 사용자에게 어떤 속성인지 설명할 수 있게된다.
Custom Annotation 관련 포스팅을 하게 된 이유는 Hilt를 직접 만들어보고 싶어서였다. 그리하여 다음 포스팅으로 직접 Hilt를 만들기 위한 과정으로 Annotation을 통한 Code Generation을 해볼 예정이다. Hilt가 Annotation을 기반 DI를 제공하기 때문이다.