devseop08 님의 블로그

어노테이션과 리플렉션 본문

Language/Kotlin

어노테이션과 리플렉션

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

12.1 어노테이션 선언과 적용

  • 어노테이션을 사용하면 선언에 추가적인 메타 데이터(클래스 이름, 함수 이름, 파라미터 이름 등)를 연관시킬 수 있고
  • 어노테이션이 설정된 방식에 따라 소스 코드, 컴파일된 클래스 파일, 런타임에 대해 작동하는 도구를 통해 메타 데이터에 접근할 수 있다.

12.1.1 어노테이션을 적용해 선언에 표지 남기기

  • 코틀린에서 어노테이션을 적용하려면 @와 어노테이션 이름을 선언 앞에 넣으면 된다.
  • 함수나 클래스 등 다른 여러 코드 구성 요소에 어노테이션을 붙일 수 있다.
import kotlin.test.*

class MyTest {
    @Test
    fun testTrue() {
        assertTrue(1 + 1 == 2)
    }
}
  • 어노테이션의 인자를 어떻게 지정하는지 확인해보자.
  • 어노테이션의 인자는 일반 함수와 마찬가지로 인자를 괄호 안에 전달한다.
@Deprecated("Use removeAt(index) instead.", ReplaceWith("removeAt(index)"))
fun remove(index: Int) { 
    /* ... */ 
}
  • 어노테이션의 인자로는 기본 타입의 값, 문자열, 이넘, 클래스 참조, 다른 어노테이션 클래스, 그리고 이 요소들로 이루어진 배열이 쓰일 수 있다.
  • 코틀린 어노테이션 인자 지정 방식은 자바와 약간의 차이가 있다.
  1. 클래스를 어노테이션 인자로 지정: ::class를 클래스 이름 뒤에 넣어야 한다.
@MyAnnotation(MyClass::class)
@DeserializeInterface(CompanyImpl::class)
  1. 다른 어노테이션을 인자로 지정: 어노테이션의 인자로 어노테이션을 넣어줄 때 인자로 쓰이는 어노테이션 앞에 @기호를 넣지 말라
@Deprecated("Use removeAt(index) instead.", ReplaceWith("removeAt(index)"))
fun remove(index: Int) { 
    /* ... */ 
}
  1. 배열을 인자로 지정: @RequestingMapping(path=["/foo", "/bar"])처럼 각괄호를 사용한다. 대신 배열을 지정하기 위해 arrayOf 함수를 사용할 수 있다. (자바에서 선언한 어노테이션 클래스를 사용한다면 value 파라미터가 필요에 따라 자동으로 가변 길이 인자로 변할 수 있다.)

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

  • 임의의 프로퍼티를 어노테이션의 인자로 지정할 수는 없다.
  • 프로퍼티를 어노테이션 인자로 사용하려면 그 앞에 const 변경자를 붙여야 한다.
  • 컴파일러는 const가 붙은 프로퍼티를 컴파일 시점에 상수로 취급한다.
const val TEST_TIMEOUT = 10L

class MyTest {
    @Test
    @Timeout(TEST_TIMEOUT)
    fun testMethod() {
        // ... 
    }
}
  • const가 붙은 프로퍼티를 파일의 최상위나 object 안에 선언해야 하며 기본 타입이나 String 으로 초기화해야만 한다.

12.1.2 어노테이션이 참조할 수 있는 정확한 선언 지정: 어노테이션 타깃

  • 코틀린 소스 코드에서 한 선언을 컴파일한 결과가 여러 자바 선언과 대응하는 경우 자주 있다. => 코틀린 선언과 대응하는 여러 자바 선언에 각각 어노테이션을 붙여야할 때가 있다.
  1. 코틀린 프로퍼티는 기본적으로 자바 필드, 게터 메서드, 어쩌면 세터 메서드 및 그 파라미터 선언과 대응한다.
  2. 주 생성자에서 프로퍼티를 선언하면 이런 접근자 메서드와 파라미터 외에 자바 생성자 파라미터와도 대응된다.
  • 어노테이션을 붙일 때 정확하게 어떤 요소에 어노테이션을 붙일지 표시할 필요가 있다.

사용 지점 타깃

  • 사용 지점 타깃 선언을 통해 어노테이션을 붙일 요소를 정할 수 있다.
  • 사용 지점 타깃은 @ 기호와 어노테이션 이름 사이에 붙으며 어노테이션 이름과는 콜론(:)으로 분리된다.
@get:JvmName("obtainCertificate")
  • 자바에서 함수나 프로퍼티를 사용하는 방법을 변경하고 싶을 때 @JvmName 어노테이션을 사용할 수 있다.
@JvmName("performCalculation")
fun calculate(): Int {
    return (2 + 2) - 1
}
/* 자바 */
performCaculation()
class CertificateManager {
    @get:JvmName("obtainCertificate")
    @set:JvmName("putCertificate")
    var certificate: String = "------BEGIN PRIVATE KEY-----"    
}
class Foo {
    public static void main(String[] args) {
        var certManager = new CertificateManager();
        var cert = certManager.obtainCertificate(); // 게터
        certManager.putCertificate("------BEGIN PRIVATE KEY-----") // 세터
    }
}
  • 사용 지점 타깃 유형
    • property: 프로퍼티 전체(자바에서 선언된 어노테이션에는 이 사용 지점 타깃을 지정할 수 없음)
    • field: 프로퍼티에 의해 생성된 필드
    • get: 프로퍼티 게터
    • set: 프로퍼티 세터
    • receiver: 확장 함수나 확장 프로퍼티의 수신 객체 파라미터
    • param: 생성자 파라미터
    • setparam: 세터 파라미터
    • delegate: 위임 프로퍼티의 위임 인스턴스를 담아둔 필드
    • file: 파일 안에 선언된 최상위 함수와 프로퍼티를 담아두는 클래스
  • file 대상을 사용하는 어노테이션은 파일에서 package 선언보다 더 앞에만 넣을 수 있다.

자바 API를 어노테이션으로 제어하기

  • 코틀린은 코틀린으로 선언한 내용을 자바 바이트코드로 컴파일하는 방법과 코틀린 선언을 자바에 노출하는 방법을 제어하기 위한 어노테이션을 많이 제공한다.
  • 이런 어노테이션 중 일부는 자바 언어의 일부 키워드를 대신한다(@Volatile 어노테이션은 자바의 volatile 키워드를 그대로 대신한다.)
  1. @JvmName: 코틀린 선언이 만들어내는 자바 필드나 메서드 이름을 변경
  2. @JvmStatic: object 키워드를 이용한 객체 선언이나 companion object 키워드를 이용한 동반 객체의 메서드에 적용하면 메서드가 자바 정적 메서드로 노출된다.
  3. @JvmOverloads: 디폴트 파라미터 값이 있는 함수에 대해 컴파일러가 자동으로 오버로딩한 함수를 생성
  4. @JvmField: 프로퍼티에 사용하면 대상 프로퍼티를 게터나 세터가 없는 공개된 public 자바 필드로 노출시킨다.
  5. @JvmRecord: data class에 사용하면 자바 레코드 클래스를 선언할 수 있다.

12.1.3 어노테이션을 활용해 JSON 직렬화 제어

  • 직렬화는 객체를 저장 장치에 저장하거나 네트워크를 통해 전송하기 위해 텍스트나 이진 형식으로 변환하는 것이다.
  • 반대 과정인 역직렬화는 텍스트나 이진 형식으로 저장된 데이터에서 원래의 객체를 만들어낸다.
  • 제이키드라는 순수 코틀린 직렬화 라이브러리를 구현해보자
  • 제이키드 라이브러리 직렬화, 역직렬화 테스트
data class Person(val name:String, val age: Int)

fun main(){
    val person = Person("Alice", 29)
    println(serialize(person))
    // {"age" : 29, "name": "Alice"}
}
fun main() {
    val json = """{"name": "Alice", "age": 29}"""
    println(deserialize<Person>(json))
    // Person(name=Alice, age=29)
}

'Language > Kotlin' 카테고리의 다른 글

[OOP] 5. 제네릭스  (2) 2025.07.21
[OOP] 4. 연산자 오버로딩  (1) 2025.07.14
[API] 3. 고차 함수  (2) 2025.07.14
[API] 2. 컬렉션과 시퀀스  (1) 2025.07.07
[OOP] 3. 기본 타입, 컬렉션, 배열  (1) 2025.06.09