애노테이션 선언과 적용

애노테이션 적용

  • 코틀린에서 @Deprecated 애노테이션은 대신할 수 있는 패턴을 제시할 수 있다.
@Deprecated("Use new(index) instead.", ReplaceWith("new(index)"))  
fun deprecated(index: Int) {  
   println("deprecated: index")  
}  
  
fun new(index: Int) {  
   println("new: $index")  
}

  • 애노테이션의 인자로는 원시 타입의 값, 문자열, enum, 클래스 참조, 다른 애노테이션 클래스, 그리고 지금까지 말한 요소들로 이뤄진 배열이 들어갈 수 있다.
    • 클래스 애노테이션 인자로 지정할 때는 @MyAnnotation(MyClass::class) 처럼 ::class를 클래스 이름 뒤에 넣어야 한다.
    • 다른 애노테이션을 인자로 지정할 때는 인자로 들어가는 애노테이션의 이름 앞에 @를 넣지 말아야된다. 앞에서 본 예제의 ReplaceWith는 애노테이션이다.
    • 배열을 인자로 사용하는 경우 자동으로 가변 길이 인자로 변환된다.
  • 애노테이션 인자를 컴파일 시점에 알 수 있어야 한다.
  • 프로퍼티를 애노테이션 인자로 사용하려면 그 앞에 const 변경자를 붙여야한다.
    • 컴파일러는 const 가 붙은 프로퍼티를 컴파일 시점 상수로 취급한다.
    • const 가 붙은 프로퍼티는 파일의 맨위나 object 안에 선언해야 하며, 원시 타입이나 String으로 초기화해야만 한다.

애노테이션 대상

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

  • 사용 지점 대상을 지정할 때 지원하는 대상 목록은 다음과 같다.
    • property: 프로퍼티 전체. 자바에서 선언된 애노테이션은 이 사용 지점 대상을 사용할 수 없다.
    • field: 프로퍼티에 의해 생성되는 (뒷받침하는) 필드
    • get: 프로퍼티 게터
    • set: 프로퍼티 세터
    • receiver: 확장 함수느 프로퍼티의 수신 객체 파라미터
    • param: 생성자 파라미터
    • setparam: 세터 파라미터
    • delegate: 위임 프로퍼티의 위임 인스턴스를 담아둔 필드
    • file: 파일 안에 선언된 최상위 함수와 프로퍼티를 담아두는 클래스
  • file 대상을 사용하는 애노테이션은 package 선언 앞에서 파일의 최상위 수준에서만 사용할 수 있다.
    • 파일에 있는 최상위 선언을 담는 클래스의 이름을 바꿔주는 @file:JvmName("StringFunctions")
  • 애노테이션 인자로 임의의 식을 허용한다.

  • 자바 API를 애노테이션으로 제어하기
    • @JvmName: 코틀린 선언이 만들어내는 자바 필드나 메소드 이름을 변경한다.
    • @JvmStatic: 메소드, 객체 선언, 동반 객체를 자바 정적 메소드로 노출한다.
    • @JvmOverloads: 디폴트 파라미터 값이 있는 함수에 대해 컴파일러가 자동으로 오버로딩한 함수를 생성해준다.
    • @JvmField: 프로퍼티에 사용하면 게터나 세터가 없는 public 자바 필드로 프로퍼티를 노출시킨다.

애노테이션 선언

  • 자바와 코틀린 애노테이션에서 value는 특별하게 취급한다.
    • 애노테이션을 적용할 때 value를 제외한 모든 애트리뷰트에는 이름을 명시해야 한다.
annotation class JsonExclude(val name: String)

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

  • 메타애노테이션: 애노테이션 클래스에 적용할 수 있는 애노테이션
  • 대표적은 메타애노테이션 @Target
    • 애노테이션을 적용할 수 있는 요소의 유형을 지정한다.
    • PROPERTY, METHOD, ANNOTATION_CLASS, FILED
@Target(AnnotationTarget.PROPERTY)  
annotation class JsonExclude(val name: String)
  • @Retention 애노테이션
    • 기본적으로 애노테이션은 .class 파일에는 저장되지만 런타임에는 사용할 수 없게 된다.
    • @Retention은 정의 중인 애노테이션 클래스를 소스 수준에서만 유지할지, .class 파일에 저장할지, 실행 시점에 리플렉션을 사용해 접근할 수 있게 하지 지정하는 메타애노테이션이다.
    • 코틀린에서는 기본적으로 애노테이션의 @RetentionRUNTIME으로 지정한다.

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

  • 클래스 참조를 파라미터로 하는 애노테이션 클래스를 선언할 수 있다.

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

  • 제네릭 클래스를 파라미터로 하는 애노테이션 클래스를 선언할 수 있다.

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

  • 리플렉션: 실행 시점에 객체의 프로퍼티와 메소드에 접근할 수 있게 해주는 방법
  • 코틀린에서 리플렉션을 사용하려면 두 가지 서로 다른 리플렉션 API를 다뤄야 한다.
    • 자바가 java.lang.reflection 패키지를 통해 제공하는 표준 리플렉션.
    • 크틸린이 kotlin.reflection 패키지를 통해 제공하는 코틀린 리플렉션. 현재 코틀린 리플렉션 API는 자바 리플렉션 APi를 완전히 대체할 수 있는 복잡한 기능을 제공하지는 않는다.

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

  • KClass를 사용하면 클래스 안에 있는 모든 선언을 열거하고 각 선언에 접근하거나 클래스의 상위 클래스를 얻는 등의 작업이 가능하다.
    • MyCalss::class라는 식을 쓰면 KClass의 인스턴스를 얻을 수 있다.
    • 실행 시점에 객체의 클래스를 얻으려면 객체의 javaClass 프로퍼티를 사용해 객체의 자바 클래스를 얻어야 한다.

  • 클래스의 모든 멤버 목록이 KCallable 인스턴스의 컬렉션이다.
  • KCallable은 함수와 프로퍼티를 아우르는 공통 상위 인터페이스다.
    • call()을 사용하면 함수나 프로퍼티의 게터를 호출할 수 있다.
    • call() 메소드를 호출할 때 넘긴 인자와 원래 함수에 정의된 파라미터 개수가 맞아 떨어지지않으면 IllegalArgumentException이 발생한다.

  • 함수를 호출하기 위해 더 구체적인 메소드를 사용할 수도 있다.
    • KFunctionN 인터페이스는 컴파일러가 생성한 합성 타입이다. 따라서, kotlin.reflect 패키지에서 이런 타입의 정의를 찾을 수는 없다.
    • call() 대신 invoke() 메소드를 호출할 떄는 인자 개수나 타입이 맞아 떨어지지 않으면 컴파일이 안된다. 따라서 KFunction의 인자 타입과 반환 타입을 모두 다 안다면 invoke 메소드를 호출하는 게 낫다.
  • KPropertycall 메소드를 호출하면 프로퍼티의 게터를 호출한다.
    • 프로퍼티 인터페이스는 프로퍼티 값을 얻는 더 좋은 방법으로 get 메소드를 제공한다.
    • 최상위 프로퍼티는 KProperty0 인터페이스의 인스턴스로 표현되며 KProperty0 안에 인자가 없는 get 메소드가 있다.
    • 멤버 프로퍼티는 Kproperty1 인스턴스로 표현된다. 그 안에는 인자가 1개인 get 메소드가 들어있다.
    • 멤버 프로퍼티는 어떤 객체에 속해 있는 프로퍼티이므로 멤버 프로퍼티의 값을 가져오려면 객체 인스턴스를 넘겨야 한다.
  • 최상위 수준이나 클래스 안에 정의된 프로퍼티만 리플렉션으로 접근할 수 있고 함수의 로컬 변수에는 접근할 수 없다.

애노테이션을 활용한 직렬화 제어

  • KAnnotatedElement 인터페이스에는 annotations 프로퍼티가 있다.
  • KPropertyKAnnotatedElement를 확장하므로 프로퍼티의 모든 애노테이션을 얻을 수 있다.
  • findAnnotation 함수는 인자로 전달받은 타입에 해당하는 애노테이션이 잇으면 그 애노테이션을 반환한다.
  • 클래스와 객체는 모두 KClass 클래스로 표현된다.
    • 객체에는 object 선언에 의해 생성된 싱글턴을 가리키는 objectInstance 라는 프로퍼티가 있다.
    • KClass가 일반 클래스를 표현한다면 createInstance를 호출해서 새 인스턴스를 만들어야 한다.

JSON 파싱과 객체의 역직렬화

  • KCallable.callBy() 각 파라미터에대한 인자의 매핑 정보를 함께 담아 호출한다.