uncategorized

애노테이션과 리플렉션

애노테이션 선언과 사용

애노테이션 적용

코틀린 애노테이션 적용은 자바와 매우 비슷하다.

1
2
@Deprecated("Use removeAt(index) instead", ReplaceWith("removeAt(index)"))
fun remove(index: Int) { ... }

애노테이션에 인자로는 원시 타입 값, 문자열, enum, 클래스 참조, 다른 어노테이션 클래스, 마지막으로 앞서 말한 요소의 배열을 전달해줄 수 있다.
다만 인자를 전달할 때는 자바와 다른 점이 있다.

  • 클래스를 인자로 전달할 때 ::class로 전달한다
  • 어노테이션 인자로 다른 어노테이션을 전달할 때는 @를 빼고 전달한다.
  • 배열을 인자로 전달하려면 arrayOf 함수를 사용한다. 다만 가변인자로 전달할 때는 그러지 않아도 된다.

어노테이션 인자를 컴파일 시점에서 알아야 한다. 따라서 어노테이션 인자로 프로퍼티를 전달할 때 임시 프로퍼티나 일반 프로퍼티는 사용하지 못한다. const가 붙은 프로퍼티를 인자로 전달할 수 있다.

어노테이션 대상

사용 지점 대상 선언으로 어노테이션을 붙일 요소를 정할 수 있다.

1
@get:Rule

위 예시를 보면 앞에 사용 지점 대상을 적고 콜론으로 어노테이션 이름적는다. 이런 방식으로 getter 메서드에 어노테이션을 적용해야 되는 경우를 프로퍼티를 통해 적용할 수 있다.

사용 지점 대상 목록

  • property : 프로퍼티 전체.
  • field ; 프로퍼티에 생성되는 필드(뒷받침하는 경우도 포함)
  • get : 프로퍼티 게터
  • set : 프로퍼티 세터
  • receiver : 확장 함수나 프로퍼티의 수신 객체 파라미터
  • param : 생성자 파라미터
  • setparam : 세터 파라미터
  • delegate : 위임 프로퍼티의 위임 인스턴스를 담아둔 필드
  • file : 파일 안에 선언된 최상위 함수와 프로퍼티를 담아둔 클래스

어노테이션 선언

객체를 직렬화하는 제이키드 라이브러리의 어노테이션을 예제로 어노테이션 선언에 대해 알아본다.

1
annotation class JsonExclude

어노테이션 클래스는 오직 메타데이터의 구조를 정의하기 때문에 내부에 아무 코드도 들어있을 수 없다.

파라미터가 있는 어노테이션은 주 생성자를 통해 파라미터를 정의한다.

1
annotation class JsonName(val name: String)

이를 자바 어노테이션 선언과 비교하면 다음과 같다.

1
2
3
public @interface JsonName {
String value();
}

메타어노테이션: 어노테이션을 처리하는 방법 제어

어노테이션 클래스에 적용할 수 있는 어노테이션을 메타어노테이션이라고 부른다.
표준 라이브러리에 있는 메타어노테이션 중 가장 흔히 쓰이는 메타어노테이션은 @Target이다.

1
2
@Target(AnnotationTarget.PROPERTY)
annotation class JsonExclude

@Target 메타어노테이션은 어노테이션을 적용할 수 있는 요소 유형을 지정한다.

메타어노테이션을 직접 만들어야 한다면 다음과 같이 구현한다.

1
2
3
4
5
6
@Target(AnnotationTarget.ANNOTATION_CLASS)
annotation class BindingAnnotation

//사용
@BindingAnnotation
annotation class MyBinding

어노테이션 파라미터로 클래스 사용

어떤 클래스를 선언 메타데이터로 참조할 수 있는 기능이 필요한 때가 있다. 클래스 참조를 파라미터로 하는 어노테이션 클래스를 선언하면 그런 기능을 사용할 수 있다.
제이키드 라이브러리의 @DeserializeInterface를 통해 이해해보자. 이 어노테이션은 인터페이스 타입인 프로퍼티를 역직렬화할 때 사용한다.

1
2
3
4
data class Person (
val name: String,
@DeserailizeInterface(CompanyImpl::class) val company: Company
)

이제 이 어노테이션의 정의를 살펴보자.

1
annotation class DeserializeInterface(val targetClass: KClass<out Any>)

KClass는 java.lang.Class 타입과 같은 역할을 하는 코틀린 타입이다. 여기서 제네릭 타입에 out이 붙었다. out이 붙지 않고 KClass<Any>로 하면 해당 어노테이션에 CompanyImpl::class를 인자로 넘기지 못하고 Any::class만 인자로 넘길 수 있다. out이 없으면 하위타입이 되지 못하기 때문이다!

어노테이션 파라미터로 제네릭 클래스 받기

파라미터로 제네릭 클래스를 받는 어노테이션을 정의해보자.

1
2
3
annotation class foo(
val barClass: KClass<out SomeClass<*>>
)

제네릭 클래스인 SomeClass를 구현한 클래스만 파라미터로 받을 수 있게 구현된 예시이다.
여기서 아직 어떤 타입이 인자로 올 지 확정할 수 없기 때문에 스타 프로젝션이 적용됐다.

리플렉션: 실행 시점에 코틀린 객체 내부 관찰

리플렉션은 실행 시점에 동적으로 객체의 프로퍼티와 메서드에 접근할 수 있게 해주는 방법이다.
리플렉션은 타입과 관계없이 객체를 다뤄야 하거나 객체가 제공하는 메서드나 프로퍼티 이름을 오직 실행 시점에서 알 수 있는 경우(JSON 직렬화)에 리플렉션이 필요하다.
코틀린에서는 java.lang.reflect 패키지를 통해 제공되는 표준 자바 리플렉션과 kotlin.reflect 패키지를 통해 제공되는 코틀린 리플렉션 API를 다룬다. 코틀린 리플렉션은 자바 리플렉션의 복잡한 기능을 아직 지원하지 않는 경우가 있어서 아직 자바 리플렉션을 필요로 한다. 다만 코틀린 리플렉션이 자바 클래스도 다룰 수 있다.

코틀린 리플렉션 API: KClass, KCallable, KFunction, KProperty

KClass

KClass는 java.lang.class에 해당하며 클래스 안에 있는 모든 선언을 열거하고 각 선언에 접근하거나 클래스의 상위 클래스를 얻는 등 작업이 가능하다. 특정 인스턴스의 KClass를 얻기 위해서는 {instance}.javaClass.kotlin으로 얻을 수 있다.

KCallable

KCallable은 함수와 프로퍼트를 아우르는 공통 상위 인터페이스다. 그 안에는 call 메서드가 있다. call 메서드를 호출하면 함수나 프로퍼티의 게터를 호출할 수 있다.

1
2
3
4
interface KCallable<out R> {
fun call(vararg args: Any?): R
...
}

KFunction

KFunction는 함수의 상위 인터페이스이다. call로 메서드를 호출할 수 있는데 이때 매개변수 갯수가 안맞으면 예외가 발생한다. 이를 방지 하기위해 KFunctionN<…>을 사용할 수 있다. 이 인터페이스는 invoke함수를 통해 정해진 매개변수를 받도록 제한한다. 다만 이런 경우 적용가능한 메서드가 적다.

KProperty, KMutableProperty

KProperty는 call로 프로퍼티의 게터를 호출한다. 하지만 프로퍼티 인터페이스는 더 좋은 방법으로 게터를 호출할 수 있다.
KProperty0은 최상위 프로퍼티 인스턴스를 담을 수 있는 인터페이스다. 이 인터페이스에 get메서드가 정의되어 있다.
KProperty1은 멤버 프로퍼티 인스턴스를 담을 수 있는 인터페이스다. 멤버 프로퍼티는 객체가 있어야 가능하므로, 인자가 하나인 get 메서드가 정의되어 있다. 그리고 제네릭 클래스이기 때문에 수신 객체의 타입을 검증할 수 있다.
KMutableProperty0, KMutableProperty1은 각 KProperty0, KProperty1를 확장한 인터페이스로 세터를 제공한다.

Share