어노테이션이란 코드에 부가 정보를 달기 위해 클래스, 함수, 프로퍼티 선언 앞에 추가하는 구문이다. 어노테이션은 @ 기호로 시작하며 사용 목적은 다음과 같다.
- 컴파일러에게 코드 문법 에러를 체크하기 위한 정보 제공
- 개발 툴이나 빌더에게 코드 자동 추가를 위한 정보 제공
- 실행 시 특정 기능을 실행하기 위한 정보 제공
작성 및 이용
일반 클래스와 동일하게 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는 허용하지 않는다. 어노테이션에 데이터를 명시하기 위해 주 생성자의 프로퍼티 선언에 사용할 수 있는 데이터 타입은 다음과 같다.
- 자바의 기초 타입(Int, Long, etc)
- String
- Classes(Foo::class)
- enums
- 다른 어노테이션
- 위에 열거한 유형의 배열
annotation class TestAnnotation1(val count: Int) annotation class TestAnnotation2(val otherAnn: TestAnnotation1, val arg1: KClass<*>) class User annotation class TestAnnotation3(val user: User) // Error! @TestAnnotation1(10) @TestAnnotation2(TestAnnotation1(20), String::class) class Test { } const val myData: Int = 10 @TestAnnotation1(myData) class Test2 { }
어노테이션을 다른 어노테이션의 주 생성자 프로퍼티로 지정할 때는 @ 기호를 추가하지 않는다. 그리고 다른 클래스를 어노테이션의 주 생성자 프로퍼티로 지정할 때는 타입을 KClass<>으로 지정해야 한다. KClass<>로 지정하면 컴파일러가 자동으로 전달받는 클래스 타입으로 변경한다.
어노테이션을 선언하면서 주 생성자를 명시했더라도, 주 생성자의 프로퍼티 타입이 클래스 객체 타입이라면 에러가 발생한다. (TestAnnotation3)
어노테이션을 이용하면서 데이터를 명시할 때 값이 아닌 변수로 명시할 수도 있는데 그 변수는 꼭 const로 선언해야 한다. 즉, 어노테이션에 전달한 값은 상수여야 한다.
어노테이션 옵션
어노테이션은 클래스, 함수, 프로퍼티에 추가해서 이용할 수 있다. 그런데 어노테이션을 이용해 특정 영역으로 제한 할 수 있다. 즉, 어떤 어노테이션은 클래스에만 추가할 수 있고 함수나 프로퍼티에는 추가할 수 없게 제한할 수 있다.
- @Target: 어느 곳에 사용하는 어노테이션인지 설정(classes, functions, properties, expressions)
- @Retention: 어노테이션을 컴파일한 클래스에 보관할지, 런타임 때 리플렉션에 의해 접근할 수 있는지 설정(source, binary, runtime)
- @Repeatable: 이 어노테이션을 한 곳에 반복 사용 가능하게 설정
- @MustBeDocumented: 어노테이션을 API에 포함해야 하는지, API 문서에 포함해야 하는지 설정
@Retention은 특히 어노테이션을 어디까지 유지할 것인지에 대한 것이다. 기본값은 모든 곳에서 어노테이션을 유지하는 것이다. 만약 옵션 값이 source라면 컴파일된 클래스에는 어노테이션이 추가되지 않는다. 주로 컴파일러에 의해 어노테이션이 추가된 곳에 특정 코드가 자동으로 만들어지게 할 때 사용한다. 컴파일러에게 무엇을 어떻게 해달라는 정보 전달을 목적으로 하므로 컴파일이 완료된 코드에는 추가될 필요가 없는 것이다.
Dagger 같은 경우는 컴파일 후에 어노테이션이 유지될 필요가 없으니 source, Retrofit 같은 경우는 컴파일 후에 어노테이션이 유지될 필요가 있으니 runtime을 사용해야 할 것 같다. (추측)
어노테이션 적용 대상 지정
프로퍼티에 추가한 어노테이션이 자바로 변형될 때 getter 함수에만 추가하고 싶을 때가 있다. 이처럼 어노테이션이 추가되는 특정 대상을 지정하는 것을 ‘이용 측 대상(Use-site Targets)’ 지정이라고 한다.
annotation class TestAnnotation
// getter 함수에만 Annotaion 추가 (Java 변환 시)
class Test {
@get:TestAnnotation
var no: Int = 10
}
코틀린에서 어노테이션 추가 대상을 지정할 수 있는 것은 다음과 같다.
- file - 코틀린 파일이 자바로 변형될 때 개발자가 정의하는 이름으로 변형되게 할 때 사용 (@file:JvmName(“MyTest”))
- property - 프로퍼티에 적용되는 이용 측 대상의 기본값. 나열한 이용 측 대상을 지정하지 않은 채로 프로퍼티의 어노테이션을 선언하면 자동으로 적용됨.
- field - 자바로 변형될 때 변수 선언에 어노테이션이 추가됨.
- get
- set
- receiver - 확장 함수나 프로퍼티에 추가됨.
- param - 생성자의 매개변수에 추가됨.
- setparam - 프로퍼티의 setter 함수 매개변수에 어노테이션이 추가됨.
여러 어노테이션을 줄 때 @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 { }