어노테이션 선언과 적용

유우선·2026년 2월 23일

Kotlin Study📚

목록 보기
32/32
  • 어노테이션 사용을 통해 선언에 추가적인 메타 데이터를 연관시킬 수 있음
  • 어노테이션 설정 방식에 따라 소스코드, 컴파일된 클래스 파일, 런타임에 대해 작동하는 도구를 통해 메타데이터에 접근할 수 있음

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

  • 어노테이션 적용 방법 → @와 어노테이션 이름을 선언 앞에 작성
@annotaionName
/*선언부*/
  • 함수, 클래스 등 여러 코드 구성 요소에 어노테이션 적용 가능

  • JUnit 프레임워크와 kotlin.test를 사용한다면 테스트 메서드 앞에 “@test”를 붙일 수 있음

import kotlin.test.*

class MyTest{
		@test // 어놑이션을 통해 JUnit 프레임워크에 이 메서드를 테스트로 호출하라고 지시
		fun testTrue() {
				assertTrue(1 + 1 == 2)
		}
} 

@Deprecated 어노테이션

  • 주석이 달린 선언을 더 이상 사용하지 않음으로 표시
  • 선언이 다른 선언에 의해 대체 되었거나 해당 기능을 더 이상 지원하지 않게 됐음
  • 최대 3가지의 파라미터를 받음
    @Target(allowedTargets = [AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CONSTRUCTOR, AnnotationTarget.PROPERTY_SETTER, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.TYPEALIAS])
    annotation class Deprecated(
        val message: String, 
        val replaceWith: ReplaceWith = ReplaceWith(""), 
        val level: DeprecationLevel = DeprecationLevel.WARNING
    )
    1. message → 사용 중단의 이유 설명 및 대체 API 사용을 권장하는 메시지
    2. replaceWith (선택적) → 더 이상 사용하지 않을 API를 대체할 코드 조각을 지정
    3. level → 점진적인 사용 중단
      • level의 단계
        1. WARNING(경고) → 사용자에게 경고를 보내지만 컴파일이나 런타임이 중단되진 않음
        2. ERROR(오류 )→ 이 선언을 사용하는 새로운 코드가 컴파일 되지 못하게 막음
        3. HIDDEN(숨김) → API가 컴파일된 코드가 남아 예전에 컴파일된 코드와의 이진 호환성을 유지
  • @Deprecated 예시
@Deprecated ("Use RemoveAt(index) instead.", ReplaceWith("removeAt(index)"))
fun remove(index: Int) { /*...*/ }
  • 이런 선언이 있는 경우 remove함수를 사용하면

어노테이션의 인자

  • 기본 타입의 값
  • 문자열 이넘
  • 클래스 참조
  • 다른 어노테이션 클래스
  • 위 요소들의 배열
  1. 클래스를 어노테이션 인자로 지정 → @MyAnnotation(MyClass::class)
  2. 다른 어노테이션을 인자로 지정
    • @Deprecated의 replaceWith도 어노테이션임
    • 어노테이션 이름 앞에 @를 붙이지 않음
  3. 배열을 인자로 지정
    • @RequestMapping(path = [”/foo”, “/bar”])의 형식
    • 각괄호를 사용하거나 arrayOf() 함수를 사용할 수 있음
    • 자바에 선언한 어노테이션 클래스를 사용하면 value 파라미터가 필요에 따라 자동으로 가변 길이 인자로 변환됨

어노테이션 인자 → 컴파일 시점에 알 수 있어야 함

  • 임의의 프로퍼티를 인자로 지정할 수 없음
  • 프로퍼티를 인자로 사용하려면 const로 선언해야 함
    • const 변경자 → 프로퍼티를 컴파일 시점 상수로 취급
	const val TEST_TIMEOUT = 10L
	
	class MyTest {
			@Test
			@Timeout(TEST_TIMEOUT)
			fun testMethod() {
					//...
			}
	}
  • const 변경자를 사용하지 않으면 “Only const val can be used in constant expression” 컴파일 오류가 발생함

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

  • 한 선언을 컴파일한 결과가 여러 자바 선언과 대응하는 경우가 자주 있음
  • 코틀린 선언과 대응하는 여러 자바 선언에 각각 어노테이션을 붙일 경우가 있음
    • 코틀린 프로퍼티 → 자바 필드, 게터 메서드, 세터 메서드 및 그 파라미터 선언과 대응
    • 주 생성자에서프로퍼티를 선언한 경우 접근자 메서드와 파라미터 외에 자바 생성자 파라미터와도 대등
  • 어노테이션을 붙일 때 이런 요소 중 어느 요소에 어노테이션을 붙일지 표시해야 함

사용 지점 타깃 선언

  • 어노테이션을 적용할 수 있는 요소의 종류 지정 (클래스, 함수, 속성, 표현식 등)
  • @와 어노테이션 이름 사이에 타깃을 지정
@target:AnnotationName

@get:JvmName("obtainCertificate")
@set:JvmName("putCertificate")
  • 자바에서 선언된 어노테이션 → 기본적으로 프로퍼티의 필드에 어노테이션을 선언함
  • 코틀린 어노테이션 → 프로퍼티에 직접 적용할 수 있는 어노테이션을 만들 수 있음

사용 지점 타깃 목록

  • property → 프로퍼티 전체 (자바에서 선언된 어노테이션에선 사용 불가)
  • field → 프로퍼티에 의해 생성되는 필드
  • get → 프로퍼티 게터
  • set → 프로퍼티 세터
  • reciver → 확장 함수나 프로퍼티의 수신 객체 파라미터
  • param → 생성자 파라미터
  • setparam → 세터 파라미터
  • delegate → 위임 프로퍼티의 위인 인스턴스를 담아둔 필드
  • file → 파일 안에 선언된 최상위 함수와 프로퍼티를 담아두는 클래스
    • package 선언보다 더 상위에 사용

@Suppress 어노테이션

  • 어노테이션 인자로 클래스, 함수 선언, 타입, 임의의 식을 받음
  • 컴파일 경고를 억제
// 안전하지 못한 캐스팅 경고 무시하는 로컬 변수 선언
fun test(list: List<*>) {
		@Suppress("UNCHECKED_CAST")
		val strings = list as List<String>
}

자바 API를 제어하는 어노테이션

  • @JvmName → 코틀린 선언이 만들어내는 자바 필드나 메서드의 이름을 변경
  • @JvmStatic → 객체 선언이나 동반 객체의 메서드에 적용하면 자바 정적 메서드로 노출됨
  • @JvmOverloads → 디폴트 파라미터가 있는 함수에 대해 자도으로 오버로딩한 함수를 생성
  • @JvmField → 대상 프로퍼티를 게터나 세터가 없는 public 자바 필드로 노출
  • @JvmRecord → data class에 사용하여 자바 레코드 클래스를 선언할 수 있음

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

  • 직렬화 → 객체를 저장 장치에 저장하거나 네트워크를 통해 전송하기 위해 텍스트나 이진 형식으로 변환
    • Json 형태로 주로 직렬화 함
    • 코틀린 객체를 직렬화하기 위한 kotlinx.serialization 라이브러리가 있음
    • Jackson, GSON 등 자바 객체를 직렬화하기 위한 라이브러리도 호환됨
  • 역직렬화 → 텍스트나 이진 형식으로 저장된 데이터에서 원래의 객체로 변환

Person 클래스 직렬화, 역질렬화 예제

data class Person(val name: String, val age: Int)

fun main() {
    val person = Person("Alice", 29)
    // 직렬화
    println(serialize(person))
    // {"age" : 29, "name" : "Alice"}
    
    // 역직렬화
    val json = """{"name" : "Alice", "age" : 29}"""
    println(deserialize<Person>(json)) // Json 객체의 타입 정보를 명시해줘야 함
    // Person(name=Alice, age=29)
}

어노테이션을 통해 직렬화, 역직렬화 방법 제어

  • Jkid 라이브러리 → 기본적으로 모든 프로퍼티를 직렬화하며 프로퍼티 이름을 키로 사용
    • 어노테이션을 통해 이런 동작을 변경할 수 있음
  1. @JsonExclude → 직렬화, 역직렬화 시 무시할 프로퍼티 지정
  2. @JsonName → 키/값 쌍의 키를 지정
data class Person(
		@JsonName("alias") val firstName: String,
		@JsonExclude val age: Int? = null
)
  • @JsonExclude로 직렬화 대상에서 제외한 프로퍼티에는 반드시 기본값을 지정해야 함
    • 역직렬화로 인스턴스를 만들 때 필요

어노테이션 선언

  • 어노테이션 클래스 → 선언이나 식과 관련 있는 메타데이터의 구조만 정의
    • 내부에 아무 코드도 들어있을 수 없음

    • @JsonExclude 예시

      annotation class JsonExclude
  • 파라미터가 있는 어노테이션 → 클래스의 주 생성자에 파라미터를 선언
    • 일반적인 주 생성자 구문을 사용하면서 val로 선언

    • @JsonName 예시

      annotation class JsonName(val name: String)

자바 어노테이션 선언과 비교

public @interface JsonName {
		String value();
}
  • 자바 어노테이션 → value() 라는 메서드가 있음
  • 어떤 어노테이션을 적용할 떄 value를 제외한 모든 애트리뷰트에 이름을 명시해야 함

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

  • 어노테이션 클래스에도 어노테이션을 붙일 수 있음

  • 어노테이션 클래스에 적용할 수 있는 어노테이션 → 메타어노테이션

  • 메타어노테이션은 컴파일러가 어노테이션을 처리하는 방법을 제어함

  • @Target 메타어노테이션

    • 어노테이션을 적용할 수 있는 요소의 유형을 지정

    • @Target을 지정하지 않으면 모든 선언에 적용할 수 있는 어노테이션이 됨

      @Target(AnnotationTarget.PROPERTY)
      annotation class JsonExclude
    • AnnotationTarget → 어노테이션 적용 가능한 타깃이 정의된 이넘

      @Target(AnnotationTarget.CLASS, 
      				AnnotationTarget.FUNCTION,
              AnnotationTarget.TYPE_PARAMETER, 
              AnnotationTarget.VALUE_PARAMETER,
              AnnotationTarget.EXPRESSION)
    • 메타어노테이션을 직접 만들 수 있음

      @Target(AnnotationTarget.ANNOTATION_CLASS)
      annotation class BindingAnnotation
      
      @BindingAnnotation
      annotation class MyBinding
    • 대상을 PROPERTY로 지정한 어노테이션 → 자바에서 사용할 수 없음

      • AnotationTarget.FIELD를 두 번째 타깃으로 추가해야 함
      • 어노테이션을 코틀린 프로퍼티와 자바 필드에 적용할 수 있어짐

@Retention 어노테이션

  • 정의한 어노테이션의 유지 수준 지정
    • 소스 수준
    • .class 파일 수준
    • 실행 시점에 리플렉션을 통한 접근 허용
  • 자바 → .class 수준이 디폴트
    • 하지만 대부분의 어노테이션을 런타임에도 사용할 수 있어야 함
  • 코틀린 → 디폴트로 RUNTIME으로 지정

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

  • 클래스 참조를 파라미터로 하는 어노테이션 클래스를 선언하여 클래스를 어노테이션 파라미터로 사용할 수 있음
interface Company {
		val name: String
}

data class CompanyImpl(override val name: String) : Company

data class Person(
		val name: String,
		@DeserializeInterface(CompanyImpl::class) val company: Company
)
  • 역직렬화 과정에서 company 프로퍼티를 표현하는 Json을 읽으면 Json을 역직렬화 하면서 CompanyImpl의 인스턴스를 만들어 Person 인스턴스의 company 프로퍼티를 설정함

@DesirializeInterface 구현

annotation class DesirializeInterface(val targetClass: KClasee<out Any>)
  • KClass 타입 → 코틀린 클래스에 대한 참조를 저장
    • KClass의 타입 파라미터 → 인스턴스가 가리키는 코틀린 타입을 지정
    • CompanyImpl::class의 타입 → KClass
      • KClass의 하위 타입

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

  • jkid → 기본 타입이 아닌 프로퍼티를 내포된 객체로 직렬화 함

  • 이런 기본 동작을 변경하려면 직렬화하는 로직을 직접 제공하면 됨

  • @CustomSerializer

    • 커스텀 직렬화 클래스에 대한 참조를 인자로 받음
      • ValueSerializer 인터페이스를 구현한 클래스

      • 직렬화, 역직렬화 제공

        interface ValueSerializer<T> {
        		fun toJsonValue(value: T) : Any?
        		fun fromJsonValue(jsonValue: Any?): T
        }
    • Person 클래스 적용 예제
      data class Person(
      		val name: String,
      		@CustomSerializer(DateSerializer::class) val birthDate: Date
      )
    • @CustomSerializer 구현
      annotation class CustomSerializer(
      		val serializerClass: KClass<out ValueSerializer<*>>
      )
      • ValueSerializer → 제네릭 클래스 (타입 파라미터가 있음)
        • 어떤 파라미터가 쓰일지 알 수 없음 → 스타프로젝션 사용

0개의 댓글