이 글은 기존 운영했던 WordPress 블로그인 PyxisPub: Development Life (pyxispub.uzuki.live) 에서 가져온 글 입니다. 모든 글을 가져오지는 않으며, 작성 시점과 현재 시점에는 차이가 많이 존재합니다.
작성 시점: 2017-09-20
Reflection. 아니, Reflection 의 상위 개념인 Meta-Programming에 대해 말하는 것이 빠를 것 같다.
Meta-programming (메타 프로그래밍) 란, 런타임 상에서 수행해야 될 작업의 일부를 컴파일 하는 동안에 생성하는 프로그램을 말하기도 한다.
특정한 조건 하에 다양한 변화를 일으키는데, ButterKnife 에서 @BindView(R.id.txtSubmit) 라고 하고 실제 코드에서는 txtSubmit 란 변수에 값을 부여하는 행동을 하는 것이 바로 메타 프로그래밍의 대표적인 예시이다.
자바는 annotation 등으로 자기 자신이 하여금 메타 프로그래밍의 대상 언어로도 할 수 있고, 메타 프로그래밍을 하기 위한 언어인 '메타 언어' 로도 될 수 있는데, 이를 Reflaction 이라 부른다.
정리하면, 아래 그림과 같다.
안드로이드에서 가장 흔히 Reflaction 을 쓰는 경우면 흔히 해당 클래스에 있는 private field, methods 에 접근해서 값을 덮어쓰는 행위인데, 이도 마찬가지 이다.
Field selectorWheelPaint = NumberPicker.class.getDeclaredField("mSelectorWheelPaint");
selectorWheelPaint.setAccessible(true);
((Paint)selectorWheelPaint.get(this)).setColor(color);
지난 https://velog.io/@windsekirun/Kotlin-filterIsInstance-with-reflection-NumberPicker-text-color 에서 나왔던 NumberPicker 위젯의 바퀴 부분에 있는 글자 색을 바꾸는 코드인데, mSelectorWheelPaint 란 필드는 NumberPicker 클래스에서 private 로 공개 범위가 설정되어 있다.
그래서 해당 값을 찾고, 접근 가능하도록 설정해 해당 값에 직접 설정함으로서 런타임 상에서 바꿀 수 있는 것이다.
그럼 코틀린에서는 어떻게 Reflaction을 사용할까.
두 가지 방법이 있다.
기존에 주어진 Java Reflection API를 사용하거나,
Kotlin Reflection API를 사용하거나.
일단, Java Reflection API 를 사용해서 한번 기능을 수행해보자.
코틑린에서는 javaClass 라는 확장 변수로 java.lang.Class 에 접근할 수 있다.
예제 코드를 만들어보자.
class Transaction(val id: Int, val amount: Double, var description: String) {
fun validate() {
if (amount > 10000) {
println("$this is too large")
}
}
}
id, amount, description 을 필드로 가지고 있고, validate 란 메소드를 추가적으로 가지고 있다.
여기에서 해당 필드들에 private 키워드를 붙이지 않았기 때문에 디컴파일 코드 상에서는 각 필드에 대해 getter 가 보일 것이다. 추가적으로 description는 var 로 선언되었기 때문에, setter 가 있다.
그리고, 메소드 하나를 만들어서 아래의 기능을 수행하는 코드를 만들어보자.
fun introspectInstance(obj: Any) {
println("Class ${obj.javaClass.simpleName}")
println("Properties\\n")
obj.javaClass.declaredFields.forEach {
println("${it.name} of ${it.type}")
}
println()
println("Functions\\n")
obj.javaClass.declaredMethods.forEach {
println(it.name)
}
}
모양은 코틀린이지만, 분명히 java.lang.Class 에 선언된 기능을 가져다 쓴 것이다.
그러면 main 메소드에서 introspecInstance 메소드에 Transaction 을 가져다 넣으면, 아래와 같이 나온다.
Class Transaction
Properties
id of int
amount of double
description of class java.lang.String
Functions
getId
validate
getAmount
getDescription
setDescription
Process finished with exit code 0
위에 설명한 대로 필드와 함수가 나왔다.
한가지 더, 해당 값이 런타임 상에서 어떤 타입인지 알아낼 수 있는 방법이 있다.
자바의 모든 타입은 Type (java.lang.reflect) 를 구현하는데 이 메소드를 가장 많이 본 곳은 아마 Intent 관련 부분일 것이다.
우리가 Intent를 사용할 때, setClass 로 목적지에 대한 클래스를 설정할 때 어떻게 했는지 기억해보면...
intent.setClass(MainActivity.this, SecondActivity.class);
뒤 .class 는 Class 를 가리키고, Class 는 Type 를 구현하고 있는 클래스이다.
코틀린에서는 Class 를 사용하기 위해서는 아래와 같은 방법을 했어야 했는데,
intent.setClass(MainActivity.this, SecondActivity::class.java)
::class 란 것 뒤에 또 .java 를 붙인다. ::class 만으로는 KClass 를 반환하기 때문이다.
아무튼, 해당 타입의 인스턴스를 생성하지 않고도 타입을 알아보려면, 아래 코드를 쓰면 된다.
fun getType(obj: java.lang.reflect.Type) {
println(obj.typeName)
}
쓸 때는 아래와 같다.
getType(Transaction::class.java)
호출하면, com.github.windsekirun.akp.Transaction 가 나올 것이다.
여기까지가 Java Reflection API 를 이용한 방법이다.
다음에는 Kotlin Reflection API 를 사용해보려고 한다.