[kotlin] 리플렉션(Reflection)

리플렉션(Reflection)은 사전적 의미로 투영, 반사라는 뜻이다. 프로그램에서 리플렉션은 런타임 때 프로그램의 구조(객체, 함수, 프로퍼티)를 분석해 내는 기법을 이야기한다. 예를 들어 어떤 함수를 정의하는데 함수의 매개변수로 클래스 타입을 선언하고 런타임 때 매개변수로 전달된 클래스의 이름, 클래스의 함수나 프로퍼티를 동적으로 팓단하는 작업을 리플렉션이라고 한다.

코틀린에서 리플렉션을 위해서는 라이브러리가 필요하다. kotlin-reflect.jar라는 라이브러리를 의존성 설정을 통해 준비해야 한다.

클래스 타입과 레퍼런스

런타임 때 동적으로 클래스를 분석하려면 클래스에 대한 정보가 필요한데 이 클래스에 대한 정보를 클래스 레퍼런스라고 표현하며, 클래스 레퍼런스를 대입받는 곳은 클래스 타입으로 선언해야 한다.

클래스 타입은 KClass<*>로 표현하며 이곳에 대입하는 클래스 레퍼런스는 “클래스명::class”로 표현한다.

리플렉션은 클래스, 함수, 프로퍼티의 레퍼런스를 런타임 때 동적으로 분석하는 목적이다. 따라서 레퍼런스를 분석하기 위한 다양한 방법을 제공한다.

클래스 정보 분석

클래스 기본 정보 분석

val isAbstract: Boolean

val isCompanion: Boolean

val isData: Boolean

val isFinal: Boolean

val isInner: Boolean

val isOpen: Boolean

val isSealed: Boolean

생성자 분석

클래스 분석 중 클래스가 생성자를 포함하고 있는지, 생성자의 매개변수는 어떻게 선언되어 있는지 분석.

val constructors: Collection<KFunction> 모든 생성자 정보

val <T: Any> KClass.primaryConstructor: KFunction? 주 생성자 정보

    import kotlin.reflect.KClass
    import kotlin.reflect.full.primaryConstructor
    
    open class MyClass(no: Int) {
    	constructor(no: Int, name: String): this(10){}
    	constructor(no: Int, name: String, email: String): this(10){}
    }
    
    fun someFun(arg: KClass<*>){
    	val constructors = arg.constructors
    	for(constructor in constructors){
    		print("constructor.....")
    		val parameters = constructor.parameters
    		for(parameter in parameters){
    			print("${parameter.name}: ${parameter.type} ..")
    		}
    		println()
    	}
    	
    	println("primary contructor.....")
    	val primaryConstructor = arg.primaryConstructor
    	if(primaryConstructor != null){
    		val parameters = primaryConstructor.parameters
    		for(parameter in parameters){
    			print("${parameter.name}: ${parameter.type} ..")
    		}
    	}
    }
    
    fun main(args: Array<String>){
    	someFun(MyClass::Class)
    }

클래스 프로퍼티 분석

val <T: Any> KClass.declaredMemberProperties: Collection<KProperty1<T, *>> 확장된 프로퍼티를 제외한 클래스에 선언된 모든 프로퍼티 반환

val <T: Any> KClass.memberProperties: Collection<KProperty1<T, *>> 확장 프로퍼티를 제외한 클래스와 상위 클래스에 선언된 모든 프로퍼티 반환

val <T: Any> KClass.declaredMemberExtensionProperties: Collection<KProperty2<T, *, *>> 클래스에 선언된 확장 프로퍼티 만을 반환

val KClass.memberExtensionProperties: Collection<KProperty2<T, *, *>> 상위 클래스 및 현 클래스의 확장 프로퍼티 만을 반환

클래스 함수 분석

val KClass<>.declaredMemberFunctions: Collection<KFunction<» 확장된 함수를 제외한 클래스에 선언된 모든 함수 반환

val KClass<>.memberFunctions: Collection<KFunction<» 확장 함수를 제외한 클래스와 상위 클래스에 선언된 모든 함수 반환

val KClass<>.declaredMemberExtensionFunctions: Collection<KFunction<» 클래스에 선언된 확장 함수 만을 반환

val KClass<>.memberExtensionFunctions: Collection<KFunction<» 상위 클래스 및 현 클래스의 확장 함수 만을 반환

함수 레퍼런스와 프로퍼티 레퍼런스

함수 레퍼런스

함수 레퍼런스는 “::함수명”을 이용한다. 예를 들어 myFun()이라는 함수의 레퍼런스를 명시하려면 ::myFun으로 지정한다. 이런 함수 레퍼런스는 KFunction<*> 타입으로 표현한다. 클래스에 선언한 멤버 함수의 레퍼런스는 앞에 클래스명을 추가하여 MyClass::myFun2로 표현한다.

val name: String 함수 이름

val parameters: List 함수의 모든 매개변수

val returnType: KType 함수의 반환 타입

KFunction 타입을 이용해 함수 레퍼런스를 이용하는 것은 함수의 기본 정보를 추출하는 것 이외에 고차 함수 이용에도 유용하다.

    fun isOdd(x: Int): Boolean = x % 2 != 0
    
    fun reflectionFun(argFun: (Int) -> Boolean){
    	println("result: ${argFun(3)}")
    }
    
    fun main(args: Array<String>){
    	reflectionFun { n -> isOdd(n) }
    	reflectionFun(::isOdd)
    }

프로퍼티 레퍼런스

프로퍼티 레퍼런스는 “::프로퍼티명”으로 이용한다. 이런 프로퍼티 레퍼런스는 두 가지 타입으로 표현하는데 KProperty<>와 KMutableProperty<>이다. val로 선언한 프로퍼티는 KProperty, var로 선언한 프로퍼티는 KMutableProperty로 표현한다.

var로 선언한 프로퍼티의 레퍼런스는 KProperty와 KMutableProperty 타입 모두에 대입할 수 있지만, val로 선언한 프로퍼티의 레퍼런스는 KProperty에만 대입할 수 있다. 프로퍼티는 setter/getter를 포함하는 변수이므로 프로퍼티의 레퍼런스 타입을 이용하여 get(), set() 함수를 호출할 수 있다. 그런데 val로 선언한 프로퍼티의 레퍼런스로는 get() 함수만 호출할 수 있으며, var로 선언한 프로퍼티의 레퍼런스로는 get()과 set() 함수 모두 호출할 수 있다.

val getter: Getter get() 함수의 정보

val isConst: Boolean

val isLateinit: Boolean

val isAbstract: Boolean

val isFinal: Boolean

val isOpen: Boolean

val name: String 프로퍼티 이름 추출

val returnType: KType 프로퍼티 타입 추출

fun call(vararg args: Any?): R 프로퍼티 get() 호출

val setter: Setter set()에 대한 정보

    val myVal: Int = 3
    var myVar: Int = 5
    
    class MyClass {
    	val objVal: Int = 10
    	var objVar: Int = 20
    }
    
    fun reflectionProperty(obj: Any?, arg: KProperty<*>){
    	println("property name: ${arg.name}, property type: ${arg.returnType}")
    	if(obj != null){
    		println(arg.getter.call(obj))	
    	} else {
    		println(arg.getter.call())
    	}
    }
    
    fun reflectionMutableProperty(obj: Any?, arg: KMutableProperty<*>){
    	println("property name: ${arg.name}, property type: ${arg.returnType}")
    	if(obj != null){
    		arg.setter.call(obj, 40)
    		println(arg.getter.call(obj))
    	} else {
    		arg.setter.call(40)
    		println(arg.getter.call())
    	}
    }
    
    fun main(args: Array<String>) {
    	reflectionProperty(null, ::myVal)
    	reflectionMutableProperty(null, ::myvar)
    
    	val obj: MyClass = MyClass()
    	reflectionProperty(obj, MyClass::objVal)
    	reflectionMutableProperty(obj, Myclass::objVar)
    }

reflectionProperty() 함수는 val로 선언한 프로퍼티의 정보를 분석하기 위한 함수로 KProperty의 name과 returnType을 이용하여 전달받은 프로퍼티의 이름과 타입을 얻는다. 그리고 프로퍼티의 get()함수를 호출하여 프로퍼티 값을 얻는데, 이 때 이용되는 것이 getter.call() 함수이다. 클래스 멤버의 경우 객체 정보도 함께 전달해야 하므로 getter.call(obj)로 작성한다. setter도 이와 비슷하다.