2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Kotlin】リフレクションで関数を呼び出す簡単なツールの作成方法

Posted at

Kotlinでプログラムを書いていると、Javaを前提としたリフレクションによるマッピングライブラリは基本的にKotlinで動かないことから、「リフレクションか何かで動的にインスタンスを生成したい」という場面が有ります。
この記事では、そのような場面に素早く対応できるよう、リフレクションで関数を呼び出す簡単なツールの作成方法についてまとめます。

紹介する方法

この記事では、メソッドリファレンスの形で呼び出したい関数(e.g. コンストラクタ、ファクトリーメソッド)を取得し、以下の3ステップで引数の初期化・関数の呼び出しを行うことで、インスタンスを生成する方法を紹介します。

  1. 呼び出し対象の関数(KFunction)を取得する
  2. パラメータ(KParameter)を読み出す
  3. パラメータと読み出し対象を突き合わせ、引数のマップ(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)

まとめ

この記事ではリフレクションで関数を呼び出す簡単なツールの作成方法を紹介しました。

実際にマッピングツールを作る場合には、引数名と読み出し対象のマッチングのための変換や、引数の型に合わせた読み出し処理が必要になりますが、大枠はここにまとめたやり方を応用することで実装できると思います。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?