Kotlin
でプログラムを書いていると、Javaを前提としたリフレクションによるマッピングライブラリは基本的にKotlinで動かないことから、「リフレクションか何かで動的にインスタンスを生成したい」という場面が有ります。
この記事では、そのような場面に素早く対応できるよう、リフレクションで関数を呼び出す簡単なツールの作成方法についてまとめます。
紹介する方法
この記事では、メソッドリファレンスの形で呼び出したい関数(e.g. コンストラクタ、ファクトリーメソッド)を取得し、以下の3ステップで引数の初期化・関数の呼び出しを行うことで、インスタンスを生成する方法を紹介します。
- 呼び出し対象の関数(
KFunction
)を取得する - パラメータ(
KParameter
)を読み出す - パラメータと読み出し対象を突き合わせ、引数のマップ(
Map<KParameter, Any?>
)を作成し、これを用いて1を呼び出す
特殊な呼び出し方が必要になったり、高速化が必要な場合に関しては、この記事では触れません。
1. 呼び出し対象の関数(KFunction)を取得する
まず、メソッドリファレンスの取得についてです。
多くの場合、KFunction
の取得は以下の2パターンで事足りるでしょう。
class Sample(val foo: Int, val bar: String)
val constructor: KFunction<Sample> = ::Sample
class Sample(val foo: Int, val bar: String) {
companion object {
fun of(foo: Int, bar: String): Sample {
return Sample(foo, bar)
}
}
}
val factoryMethod: KFunction<Sample> = (Sample)::of
2. パラメータ(KParameter)を読み出す
次に、パラメータ(KParameter
)の読み出しについてです。
KParameter
は引数の情報をまとめたクラスです。
name
には引数名が、type.classifier
には、引数のKClass
の情報が格納されており、マッピングツールを作る場合には主にこの2つの情報が役に立ちます。
class Sample(val foo: Int, val bar: String)
val constructor: KFunction<Sample> = ::Sample
val parameters: List<KParameter> = constructor.parameters
val parameter = parameters.first()
println(parameter.name) // -> foo
println(parameter.type.classifier as KClass<*>) // -> class kotlin.Int
3. パラメータと読み出し対象を突き合わせ、引数のマップ(Map)を作成し、これを用いて1を呼び出す
最後に、読み出しと呼び出しについてです。
ここでは簡単な例として、Map<String, Any?>
から読み出した情報を用いてKFunction
を呼び出す処理を示します。
data class Sample(val foo: Int, val bar: String)
val constructor: KFunction<Sample> = ::Sample
val parameters: List<KParameter> = constructor.parameters
fun callFunction(src: Map<String, Any?>): Sample {
val argumentMap: Map<KParameter, Any?> = parameters.associateWith {
src.getValue(it.name!!)
}
return constructor.callBy(argumentMap)
}
println(
callFunction(
mapOf("foo" to 1, "bar" to "2")
)
) // -> Sample(foo=1, bar=2)
まとめ
この記事ではリフレクションで関数を呼び出す簡単なツールの作成方法を紹介しました。
実際にマッピングツールを作る場合には、引数名と読み出し対象のマッチングのための変換や、引数の型に合わせた読み出し処理が必要になりますが、大枠はここにまとめたやり方を応用することで実装できると思います。