Kotlin - Reflection (1) - With Java API

WindSekirun (wind.seo)·2022년 4월 26일
0

이 글은 기존 운영했던 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

한가지 더, 해당 값이 런타임 상에서 어떤 타입인지 알아낼 수 있는 방법이 있다.

자바의 모든 타입은 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 를 사용해보려고 한다.

profile
Android Developer @kakaobank

0개의 댓글