ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Kotlin in Action: ch 10. 애노테이션과 리플렉션
    Book/Kotlin in Action 2023. 1. 20. 09:23

    어떤 함수를 호출하려면 그 함수가 정의된 클래스의 이름과 함수 이름, 파라미터 이름 등을 알아야만 했다. 애노테이션과 리플렉션을 사용하면 그런 제약을 벗어나서 미리 알지 못하는 임의의 클래스를 다룰 수 있다. 애노테이션을 사용하면 라이브러리가 요구하는 의미를 클래스에게 부여할 수 있고, 리플렉션을 사용하면 실행 시점에 컴파일러 내부 구조를 분석할 수 있다.

     

    10.1 애노테이션 선언과 적용

    애노테이션을 사용하면 추가 메타데이터를 선언과 연결할 수 있다.

    메타데이터는 애노테이션 구성 방식에 따라 소스 코드, 컴파일된 클래스 파일 또는 런타임 시 작업하는 도구에서 액세스할 수 있다.

    • 메타데이터란 애플리케이션이 처리해야 할 데이터가 아니라, 컴파일 과정과 실행 과정에서 코드를 어떻게 컴파일하고 처리할 것인지 알려주는 정보이다.

    10.1.1 애노테이션 적용

    애노테이션의 인자로는 원시 타입의 값, 문자열, enum, 클래스 참조, 다른 애노테이션 클래스, 그리고 지금까지 말한 요소들로 이뤄진 배열이 들어갈 수 있다. 애노테이션 인자를 지정하는 문법은 자바와 약간 다르다. 

    애노테이션 인자는 컴파일 시점에 알 수 있어야한다.

     

     

    코틀린 한 소스코드의 선언을 컴파일한 결과가 여러 자바 선언과 대응하는 경우가 있다. (e.g. var로 선언한 property는 getter, setter에 대응된다)

    • 사용 지점 대상(use-site target) 선언으로 애노테이션을 붙일 요소를 정할 수 있다.
    • @get:Rule 에서
      • get → 사용 지점 대상
      • Rule → 애노테이션 이름
    • 사용 지점 대상 지원 목록
      • property: 프로퍼티 전체 - 자바에서 선언된 애노테이션에는 사용 불가
      • field: 프로퍼티에 의해 생성되는 backing 필드
      • get: 프로퍼티 게터
      • set: 프로퍼티 세터
      • receiver: 확장 함수나 프로퍼티 수신 객체 파라미터
      • param: 생성자 파라미터
      • setparam: 세터 파라미터
      • delegate: 위임 프로퍼티의 위임 인스턴스를 담아둔 필드
      • file: 파일 안에 선언된 최상위 함수와 프로퍼티를 담아두는 클래스
    • 코틀린은 자바 바이트코드로 컴파일하는 방법, 코틀린 선언을 자바에 노출하는 방법을 제어하기 위한 애노테이션 제공한다.
      • @Volatile
      • @Strictfp
      • @JvmName: 자바 필드, 메서드 이름 변경
      • @JvmStatic
      • @JvmOverloads: 디폴트 파라미터 값이 있는 함수에 대해 컴파일러가 자동으로 오버로딩한 함수 생성
      • @JvmField: getter, setter가 없는 public 자바 필드로 프로퍼티 노출

    10.1.4 애노테이션 선언

    annotation class JsonExclude
    annotation class JsonName(val name: String) // 인자가 있는 경우
    

    오직 선언이나 식과 관련 있는 메타데이터의 구조를 정의하기 때문에 내부에 아무 코드도 없다.

     

    자바 애노테이션의 value 메서드는 특별한데, 애노테이션을 적용할 때, value를 제외한 모든 애트리뷰트 이름을 명시해야하기 때문이다.

    반면 코틀린 애노테이션은 일반적인 생성자 호출과 마찬가지 방법으로 애노테이션 적용이 가능하다.

     

    10.1.5 메타 애노테이션

    • 컴파일러가 애노테이션을 처리하는 방법을 제어
    • @Target 애노테이션을 적용할 수 있는 요소의 유형을 지정한다.
      • 기본은 모든 선언에 적용 가능
      • 프로퍼티에만 애노테이션을 달고 싶으면?
      @Target(AnnotationTarget.PROPERTY)
      annotation class JsonExclude
      
      • 메타애노테이션을 만들고 싶으면?
      @Target(AnnotationTarget.ANNOTATION_CLASS)
      annotation class BindingAnnotation
      
    • @Retention 애노테이션 클래스를 소스 수준에서만 유지할지, .class 파일에 저장할지, 실행 시점에 리플렉션을 사용할 수 있게 할지 지정하는 메타애노테이션
      • 자바는 기본 .class 파일에 저장 but 런타임 사용 불가
      • 코틀린은 기본 RUNTIME 지정

    10.1.6 & 10.1.7 애노테이션 파라미터로 클래스 사용 & 제네릭 클래스 활용

    • 어떤 클래스를 선언 메타데이터로 참조할 수 있는 기능이 필요할 때도 있다.
    • e.g. 역직렬화 시 어떤 클래스를 사용해 인터페이스를 구현할 것인가 지정
    • 클래스 받기 패턴
      • 클래스를 인자로 받는 경우
      // KClass<out Any> 사용
      annotation class DeseializeInterface(val targetClass: KClass<out Any>)
      
      • 제네릭 클래스를 인자로 받는 경우
      annotation class CustomSerializer(
        val serializerClass: KClass<out ValueSerializer<*>>
      )
      

    10.2 리플랙션: 실행 시점에 코트린 객체 내부 관찰

    • 리플렉션은 실행 시점에(동적으로) 객체의 프로퍼티와 메서드에 접근할 수 있게 해주는 방법
      • 타입과 관계없이 객체를 다뤄야하는 경우
      • 객체가 제공하는 메서드나 프로퍼티 이름을 오직 실행 시점에만 알 수 있는 경우
    • 리플랙션을 사용하는 자바 라이브러리와 코틀린 코드가 완전히 호환된다. (java.lang.reflect)
    • 자바에 없는 프로퍼티, nullable 타입과 같은 코틀린 고유 개념에 대한 리플렉션 제공 (kotlin.reflect)

    코틀린 리플렉션 API

    • KClass
      • 클래스를 표현한다.
      • java.lang.Class에 해당하는 KClass
      • 모든 선언 열거, 상위 클래스 얻는 등의 작업 가능
      class Person(val name: String, val age: Int)
      
      val person = Person("Seungmin", 27)
      
      val kClass = Person::class // 클래스로부터 KClass 얻기
      val kClass = person.javaClass.kotlin // 인스턴스로부터 KClass 얻기
      
    • KCallable
      • 함수, 프로퍼티의 공통 상위 인터페이스
      • call 인터페이스를 제공해 가변 인자와 가변 반환을 할 수 있다.
    • KFunction
      • 함수 표현
      • invoke 함수를 제공해서 컴파일 타임에 인자 개수와 타입에 대한 체크를 할 수 있다.
      • KFunction1<Int, Unit>: 이런 식으로 반환 값 타입 정보가 들어있는 식으로 활용 가능
      • KFunctionN은 컴파일러가 생성한 합성 타입이므로, 이런 타입의 정의를 미리 찾을 수 없을 것이다. 컴파일러가 원하는 만큼 생성하므로 kotlin-runtime.jar 크기를 줄일 수 있고 함수 파라미터 개수에 대한 인위적인 제약을 피한다.
    • KProperty
      • 프로퍼티 표현 (함수의 로컬 변수에는 접근할 수 없다)
      • get 함수를 제공해서 프로퍼티 값을 얻을 수 있다.
      • KProperty0 최상위 프로퍼티 → 인자 없는 get 함수로 값 받아오기
      • KProperty1 멤버 프로퍼티 → 객체에 속해 있는 프로퍼티이므로 값을 받아오려면 객체 인스턴스를 넘겨야한다. 즉 인자가 1개 있는 get함수 제공

    JKid 에서 얻은 tips

    • KAnnotatedElement 인터페이스에는 annotations 프로퍼티가 있다. 해당 요소에 적용된 (@Retention이 RUNTIME으로 지정된) 모든 애노테이션 인스턴스의 컬렉션
      • 프로퍼티에 적용된 annotation을 받아오려면? property.annotations
    • 어떤 애노테이션을 찾으려면?
      • KAnnotatedElement.findAnnotation()
    • object로 선언한 instance를 받아오려면?
      • KClass.objectInstance
    • class의 인자 없거나 모든 인자가 optional한 객체를 생성하려면?
      • KClass.createInstance()
    • 디폴트 파라미터 값을 지원하고 싶으면?
      • KCallable.callBy()
      • Map<KParameter, Any?>를 인자로 넘겨야한다.
      • 인자 순서에 상관없이 함수를 호출할 수 있다.
    • 함수의 파라미터에 접근하려면?
      • KFunction.parameters: List<KParameter>
    • 함수 파라미터에 디폴트 값이 있는지 없는지 알려면?
      • KParameter.isOptional
      • true이면 디폴트 값이 있음
    • 타입이 nullable인지 아닌지 알려면?
      • KType.isMarkedNullable

     

Designed by Tistory.