반응형
이 포스팅은 Kotlin in Action 책의 10장을 정리한 글 입니다.
- Annotation과 Reflection을 사용하면 실행 시점에 컴파일러 내부 구조를 분석할 수 있다.
10.1 애노테이션 선언과 적용
10.1.1 애노테이션 적용
- 자바와 똑같은 방식으로 @를 붙여서 사용
@Deprecated("Use removeAt(index) instead", ReplaceWith("removeAt(index)"))
fun remove(index: Int) { ... }
- 자바와의 차이
- 클래스를 애노테이션 인자로 지정할 때
@MyAnnotaion(MyClass::class)
- 인자로 들어가는 애노테이션의 이름 앞에는 @를 넣지 않는다.
- 배열을 인자로 지정하려면 arrayOf 함수를 사용한다.
- 클래스를 애노테이션 인자로 지정할 때
- 어노테이션 인자는 컴파일 시점에 알 수 있어야 한다.
- 따라서 프로퍼티는 안되고 const 프로퍼티는 된다.
10.1.2 애노테이션 대상
- 애노테이션 선언 하나가 여러 개의 자바 선언과 대응할 수 있다.
- 예를 들어 프로퍼티에 붙이면 getter, setter에도 ...
- 사용 지점 대상 선언을 사용할 수 있다.
@get:MyAnnotation
getter에만 어노테이션이 붙는다.- property, field, get, set, receiver, param, setparam, delegate(위임), file
- 파일에 있는 최상위 선언을 담는 클래스의 이름을 바꿔주는 JvmName - 3.2.3 절 참고
import strings.StringFunctions; StringFunctions.joinToString(list, "")
@file:JvmName("StringFunctions") package strings fun joinToString(...): String {...}
10.1.3 애노테이션을 활용한 JSON 직렬화 제어
- 직렬화 Person("Alice", 29) → {"age": 29, "name": "Alice"}
- 역직렬화 {"age": 29, "name": "Alice"} → Person("Alice", 29)
@JsonExclude
직렬화/역직렬화 시 프로퍼티 무시@JsonName
다른 지정 이름을 쓸 수 있음- 제이키드 라이브러리에서 제공
10.1.4 애노테이션 선언
- 애노테이션 클래스는 메타데이터의 구조를 정의하므로 내부에 아무 코드도 들어있을 수 없다
pubic @interface JsonName {
String value();
}
annotation class JsonName(val name: String)
@JsonName(name= "first_name")
@JsonName("first_name")
10.1.5 메타애노테이션: 애노테이션을 처리하는 방법 제어
- 애노테이션 클래스에도 애노테이션을 붙일 수 있다 → Meta-Annotation
@Target
애노테이션을 적용할 수 있는 요소의 유형을 지정한다.- 주의할 점 ? AnnotationTarget.PROPERTY와 AnnotationTarget.FIELD는 다르다.
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.FIELD)
@Retention
코틀린에서는 기본적으로 RUNTIME으로 지정한다.
10.1.6 애노테이션 파라미터로 클래스 사용
- 정적인 데이터 외에도 클래스를 참조할 필요가 있을 수 있다.
- 클래스 참조를 파라미터로 하는 애노테이션 클래스 선언
@DeserializeInterface(CompanyImpl::class)
annotation class DeserializeInterface(val targetClass: KClass<out Any>)
- out을 쓰지 않으면 Any::class만 넘길 수 있다.
- KClass는? 10.2 참조
10.1.7 애노테이션 파라미터로 제네릭 클래스 받기
- KClass<out 클래스이름<*>>
10.2 리플렉션: 실행 시점에 코틀린 객체 내부 관찰
- 리플렉션 : 실행 시점에 동적으로 객체의 프로퍼티와 메소드에 접근할 수 있게 해주는 방법
- 보통 메서드/프로퍼티 접근시에 컴파일 시점에 이름을 찾아내서 존재를 보장함
- 그러나 JSON 직렬화 경우 프로퍼티 이름을 오직 실행 시점에만 알 수 있다.
- 코틀린에서 리플렉션 사용하려면..
- java.lang.reflect 패캐지를 통해 제공하는 표준 리플렉션 API
- 자바/코틀린 코드 완전히 호환
- kotlin.reflect 패캐지를 통해 제공하는 코틀린 리플렉션 API
- 널이 될 수 있는 타입 등 지원
- 그러나 복잡한 기능은 제공하지 않는다.. → 이건 자바 API로 해야됨
- java.lang.reflect 패캐지를 통해 제공하는 표준 리플렉션 API
10.2.1 코틀린 리플렉션 API: KClass, KCallable, KFunction, KProperty
- KClass
- 클래스 안에 있는 모든 선언 접근/상위 클래스 얻는 작업
- MyClass::class → KClass의 인스턴스 얻을 수 있다
- 또는
import kotlin.reflect.full.* val person = Person("Alice", 29) val kClass = person.javaClass.kotlin // KClass<Person> 인스턴스 반환 kClass.memberProperties.forEach{ ... }
- KCallable
- 함수와 프로퍼티를 아우르는 공통 상위 인터페이스
- call 메소드 → 함수나 프로퍼티의 게터 호출
- KFunction
- ::foo는 KFunction 클래스의 인스턴스
- 만약 인자가 맞지 않는다면 IllegArgumentException이 날 것이다.
- 더 구체적인 메소드를 사용하면 된다.
- KFunction1<Int, Unit> → 틀리면 컴파일 못하게 → 차라리 invoke를 하면 됨 (타입 안맞으면 컴파일 안되게 한다.)
- 인자 타입과 반환 타입을 모두 다 안다면 Invoke 메소드를 호출하는 게 낫다.
- call 메소드는 모든 타입의 함수에 적용할수 있는 일반적인 메소드지만 타입 안전성을 보장해주지 않지만 , invoke는 타입 체크를 해준다.
kFunction.invoke(42)
fun foo(x: Int) = println(x) val kFunction = ::foo kFunction.call(42)
- KProperty
- KProperty에 call 메소드가 존재
var counter = 0 val kProperty = ::counter kProperty.setter.call(21) println(kProperty.get()) val person = Person("Alice", 29) val memberProperty = Person::age println(memberProperty.get(person))
10.2.2 리플렉션을 사용한 객체 직렬화 구현
- serialize 함수는 작업을 serializeObject에 위임하고 StringBuilder 인스턴스.. → 람다 본문에서 인스턴스를 this로 사용할 수 있다는 장점
fun serialize(obj: Any): String = buildString { serializeObject(obj) }
private fun StringBuilder.serializeObject(obj: Any) {
val KClass = obj.javaClass.kotlin // get a kClass instance
val properties = kClass.memberProperties // get all properties
properties.joinToStringBuilder(
this, prefix = "{", postfix = "}") { prop ->
serializeString(prop.name) // get a property name
append(": ")
serializePropertyValue(prop.get(obj)) // get a property value
}
}
- 프로퍼티가 어떤 타입인지는 알 수 없기 때문에 각 prop의 타입은 KProperty1<Any, *>
10.2.3 애노테이션을 활용한 직렬화 제어
- @JsonExclude, @JsonName 이런 애노테이션을 serializeObject 함수가 어떻게 처리할까?
- KProperty에 findAnnotation 함수가 있다
- @JsonExclude
val properties = kClass.memberProperties.filter { it.findannotation<JsonExclude> () == null }
- @JsonName
val jsonNameAnn = prop.findAnnotation<JsonName>()
val propName = jsonNameAnn?.name ?: prop.name
- 459쪽 CustomSerializer 만들기 → 몰라도 될 것 같음 PASS
10.2.4 JSON 파싱과 객체 역직렬화
- 어휘분석기 렉서
- 문자열을 토큰의 리스트로 변환한다. (보통 콤마)
- 문법 분석기 파서
- 토큰의 리스트를 구조화된 표현으로 변환한다.
- 의미 단위를 만날 때마다 JsonObject의 메소드를 적절히 호출한다.
- 파싱한 결과로 객체를 생성하는 역직렬화 컴포넌트 - 역직렬화기
- JsonObject 구현 제공
- 중첩된 객체 값을 만들어낸다.
- { person: {name: '1', age: 23}}
- 구현해보기
fun <T: Any> deserialize(json: Reader, targetClass: KClass<T>): T {
val seed = ObjectSeed(targetClass, ClassInfoCache())
Parser(json, seed).parse()
return seed.spawn()
}
- ObjectSeed : 객체의 프로퍼티가 담겨져 있다.
- 파서를 호출하면서 프로퍼티를 인자로 전달하고,
- spawn 함수를 통해 결과 객체를 생성한다.
- 세부적인 구현 → PASS
10.2.5 최종 역직렬화 단계: callBy(), 리플렉션을 사용해 객체 만들기
- ClassInfo 클래스 : 최종 결과 객체 인스턴스를 생성하고 생성자 파라미터 정보를 캐시한다. ObjectSeed 클래스 안에서 사용된다.
- callBy를 사용해서 프로퍼티 맵을 받으면 1) 파라미터 순서 신경쓰지 않아도 되고 2) 값이 없으면 디폴트 값을 넣어줄 수도 있다.
interface KCallable<out R>{
fun callBy(args: Map<KParameter, Any?>): R
- 그대신 type을 체크해 주어야 함 → KParameter.type 프로퍼티 활용
- 값 타입에 따라 직렬화기 가져오기
fun serializerForType(type: Type): ValueSerializer<out Any?>? =
when(type) {
Byte::class.java -> ByteSerializer
Int::class.java -> IntSerialzier
- ClassInfoCache : 리플렉션 연산 비용을 줄이기 위한 클래스
class ClassInfoCache {
private val cacheData = mutableMapOf<KClass<*>, ClassInfo<*>>
operator fun <T:Any> get(cls: KClass<T>): ClassInfo<T> =
cacheData.getOrPut(cls) { ClassInfo(cls) } as ClassInfo<T>
- cls에 대한 항목이 있다면 반환 → 그렇지 않으면 계산&저장 후 반환
- 마찬가지로 생성자 파라미터나 애노테이션 인자로 지정한 클래스 등을 캐시해둘 수 있다.
- 필수 파라미터가 모두 있는 검증하는 코드
private fun ensureAllParameter(arguments: Map<KParameter, Any?> {
for (param in constructor.parameters) {
if (arguments[param] == null &&
!param.isOptional && !param.type.isMarkedNullable) {
throw JKidException("Missing vlue for parameter ${params.name}")
- 캐시를 사용하면? 모든 프로퍼티에 대해서 애노테이션을 찾는 과정을 반복할 필요가 없다!
반응형
'책정리 > Kotlin in Action' 카테고리의 다른 글
[Kotlin in Action] 컬렉션과 배열, (MutableCollection, 자바와의 관계) (0) | 2021.10.21 |
---|---|
[Kotlin in Action] 코틀린의 원시 타입, Any, Unit, Nothing 타입 (0) | 2021.10.21 |
[Kotlin] 클래스와 프로퍼티, Enum 클래스 - Java와 비교하기 (0) | 2021.09.02 |
[Kotlin] 함수와 변수 - Java와 비교하기 (0) | 2021.09.02 |
[Kotlin] 코틀린이란? 왜 코틀린을 사용할까? (0) | 2021.08.27 |