[kotlin] 어노테이션(annotation)

어노테이션이란 코드에 부가 정보를 달기 위해 클래스, 함수, 프로퍼티 선언 앞에 추가하는 구문이다. 어노테이션은 @ 기호로 시작하며 사용 목적은 다음과 같다.

작성 및 이용

일반 클래스와 동일하게 class라는 예약어로 선언하는데 annotation 예약어를 추가해 선언한다. 객체 생성은 불가능하고 클래스 내에 프로퍼티나 함수를 추가할 수 없다.

annotation class TestAnnotation

어노테이션을 추가하여 부가 정보가 달린 곳을 특별하게 처리해 주는 무엇인가가 있어야 한다. 그것은 컴파일러가 될 수도 있고, 클래스 로더가 될 수도 있고, 개발자 코드가 될 수도 있다.

어노테이션은 함수나 프로퍼티는 물론 주 생성자, 프로퍼티의 getter/setter에도 추가할 수 있으며 주 생성자에 어노테이션을 추가하려면 constructor 키워드를 함께 작성해야 한다.

    annotation class TestAnnotation
    
    class Test @TestAnnotation constructor() {
    	@TestAnnotation
    	val myVal: Int = 10
    	
    	var myVal2: Int = 10
    		@TestAnnotation set(value) { field = value }
    
    	// 람다에 어노테이션 추가
    	val myFun = @TestAnnotation{
    	}
    }

어노테이션 설정

데이터 설정

어노테이션에 특정 데이터를 설정하고 그 데이터를 이용할 수도 있다. 어노테이션 클래스는 구현체 {}를 포함할 수 없지만, 생성자는 포함할 수 있다. 구현체가 없으므로 보조 생성자는 불가능하며 주 생성자만 포함할 수 있고, 주 생성자를 이용해 특정 데이터를 설정하는 어노테이션을 만들 수 있다.

    annotation class TestAnnotataion(val count: Int)
    
    class Test {
    	@TestAnnotation(count=3)
    	fun some(){
    		println("some.....")
    	}
    }
    
    fun main(args: Array<String>){
    	val obj: Test = Test()
    	
    	val methods = Test::class.java!!.methods
    
    	for(method in methods){
    		if(method.isAnnotationPresent(TestAnnotation::class.java)){
    			val annotation = method.getAnnotation(TestAnnotation::class.java)
    			val count = annotation..count
    			for(i in 1..count){
    				obj.some()
    			}
    		}
    	}
    }
    
    // some.....
    // some.....
    // some.....

어노테이션 생성자의 매개변수에는 꼭 val을 추가해야 한다. var는 허용하지 않는다. 어노테이션에 데이터를 명시하기 위해 주 생성자의 프로퍼티 선언에 사용할 수 있는 데이터 타입은 다음과 같다.

어노테이션을 선언하면서 주 생성자를 명시했더라도, 주 생성자의 프로퍼티 타입이 클래스 객체 타입이라면 에러가 발생한다. (TestAnnotation3)

어노테이션을 이용하면서 데이터를 명시할 때 값이 아닌 변수로 명시할 수도 있는데 그 변수는 꼭 const로 선언해야 한다. 즉, 어노테이션에 전달한 값은 상수여야 한다.

어노테이션 옵션

어노테이션은 클래스, 함수, 프로퍼티에 추가해서 이용할 수 있다. 그런데 어노테이션을 이용해 특정 영역으로 제한 할 수 있다. 즉, 어떤 어노테이션은 클래스에만 추가할 수 있고 함수나 프로퍼티에는 추가할 수 없게 제한할 수 있다.

@Retention은 특히 어노테이션을 어디까지 유지할 것인지에 대한 것이다. 기본값은 모든 곳에서 어노테이션을 유지하는 것이다. 만약 옵션 값이 source라면 컴파일된 클래스에는 어노테이션이 추가되지 않는다. 주로 컴파일러에 의해 어노테이션이 추가된 곳에 특정 코드가 자동으로 만들어지게 할 때 사용한다. 컴파일러에게 무엇을 어떻게 해달라는 정보 전달을 목적으로 하므로 컴파일이 완료된 코드에는 추가될 필요가 없는 것이다.

Dagger 같은 경우는 컴파일 후에 어노테이션이 유지될 필요가 없으니 source, Retrofit 같은 경우는 컴파일 후에 어노테이션이 유지될 필요가 있으니 runtime을 사용해야 할 것 같다. (추측)

어노테이션 적용 대상 지정

프로퍼티에 추가한 어노테이션이 자바로 변형될 때 getter 함수에만 추가하고 싶을 때가 있다. 이처럼 어노테이션이 추가되는 특정 대상을 지정하는 것을 ‘이용 측 대상(Use-site Targets)’ 지정이라고 한다.

    annotation class TestAnnotation
    
    // getter 함수에만 Annotaion 추가 (Java 변환 시)
    class Test {
    	@get:TestAnnotation
    	var no: Int = 10
    }

코틀린에서 어노테이션 추가 대상을 지정할 수 있는 것은 다음과 같다.

여러 어노테이션을 줄 때 @get:[TestAnnotation TestAnnotation2]

자바 어노테이션 이용

자바로 선언된 어노테이션은 코틀린에서 그대로 사용할 수 있다.

    public @interface JavaAnnotation { }
    @JavaAnnotation
    class Test { }

문제는 데이터 설정이 되어야 할 때 순서의 문제가 발생한다는 것이다. 코틀린은 어노테이션의 주 생성자 프로퍼티를 이용하여 데이터를 설정하므로 프로퍼티가 선언된 순서로 데이터를 지정해주면 된다. 그러나 자바 어노테이션의 데이터는 함수로 표현하므로 순서가 없다. 즉, 데이터를 전달할 때 반드시 이름을 명시해야 한다.

    public @interface JavaAnnotation {
    	int intValue();
    	String stringValue();
    }
    annotation class KotlinAnnotation(val no: Int, val name: String)
    
    @KotlinAnnotation(10, "son")
    @JavaAnnotation(intValue = 10, stringValue = "son")
    class Test { }

자바 어노테이션의 함수명이 value이면 이 때는 이름을 명시하지 않아도 된다. 일종의 기본값 정도의 개념으로 사용되는 것이 value이다. 배열의 함수가 value()로 선언되었다면 데이터만 나열해서 전달하면 되는데 value() 함수가 아닐 때는 arrayOf() 함수를 이용하여 데이터를 전달한다.

    public @interface JavaAnnotation {
    	int value();
    	String strValue();
    }
    
    public @interface JavaAnnotation2 {
    	int[] value();
    	String[] strValue();
    }
    @JavaAnnotation(10, strValue = "son")
    @JavaAnnotation2(10, 20, strValue = arrayOf("son", "jk"))
    class Test { }