TL;DR
-
Kotlin
のデフォルト引数付き関数はJava
のリフレクションからも呼び出せる =kotlin-reflect
無しでも呼び出せる - ただし、
kotlin-reflect
無しで呼び出しのための情報を集めることは困難が伴うため、依然としてkotlin-reflect
の削除は難しい
本文
デフォルト引数付き関数がどうコンパイルされるか
まず、デフォルト引数付き関数がどうコンパイルされるかを見ていきます。
例として、以下のようなデフォルト引数付きコンストラクタを取り上げます。
data class Clazz(val foo: String = "Hello World.")
このコンストラクタは以下のようにコンパイルされます。
public Clazz(@NotNull String foo) {
Intrinsics.checkNotNullParameter(foo, "foo");
super();
this.foo = foo;
}
// $FF: synthetic method
public Clazz(String var1, int var2, DefaultConstructorMarker var3) {
if ((var2 & 1) != 0) {
var1 = "Hello World.";
}
this(var1);
}
2番目に出ているコンストラクタでは、条件分岐でデフォルト引数が設定されていることが分かります。
ここで、引数は以下のような意味を持っています。
- コード上で設定した引数
- 引数が設定されていれば0、設定されていなければ1となるビットマスク
- デフォルト引数を利用することを示すマーカー(基本的に
null
を渡せばOK)
実際に呼び出してみる
このデフォルト引数付きコンストラクタは、コード上からは呼び出せませんが、Java
のリフレクションからは呼び出すことができます。
実際に呼び出してみたコードが以下です。
import java.lang.reflect.Constructor
data class Clazz(val foo: String = "Hello World.")
fun main() {
val clazz: Class<Clazz> = Clazz::class.java
val constructor: Constructor<Clazz> = clazz.constructors.first { constructor ->
// デフォルトコンストラクタの引数末尾のクラス名は必ずDefaultConstructorMarker
val isDefaultConstructor = constructor.parameters.lastOrNull()
?.let { it.type.name == "kotlin.jvm.internal.DefaultConstructorMarker" }
?: false
// 今回の引数サイズは1、マスクとDefaultConstructorMarkerで2引数、合計3引数になる
val isCollectArgumentSize = constructor.parameters.size == 3
isDefaultConstructor && isCollectArgumentSize
} as Constructor<Clazz>
println(constructor.newInstance("こんにちは。", 0, null))
println(constructor.newInstance("こんにちは。", 1, null))
println(constructor.newInstance("こんにちは。", 2, null))
}
このコードを呼び出すと以下のような実行結果となります。
適切なビットマスクを設定した場合、渡した引数は無視され、デフォルト引数が用いられていることが分かります。
Clazz(foo=こんにちは。)
Clazz(foo=Hello World.)
Clazz(foo=こんにちは。)
これがどう役立つか
Kotlin
のリフレクションでデフォルト引数を用いた関数呼び出しを行う場合、kotlin-reflect
モジュールを使ってcallBy
するのがほぼ唯一の方法です。
一方、kotlin-reflect
モジュールは容量が重く、環境によっては扱いにくさが有ります。
今回紹介した方法を上手く使うことができれば、リフレクションを用いて関数呼び出しするツールからkotlin-reflect
を削除することができるかもしれません。
ただし、実際にこれをやる場合、kotlin-reflect
無しのKFunction
からはパラメータ情報が取得できない、Java
のリフレクション上のConstructor
/Method
を取得できないといった事情が有るため、単純にkotlin-reflect
を削除できるとはなりません。
まだ詰め切れていませんが、自分が分かっている限りではkapt
+ KotlinPoet
みたいな感じで事前に処理できる場合には役に立つかもという程度です。
参考にさせて頂いた内容
下記コードの中で登場するTestClass
に対して生成されるTestClassJsonAdapter
のfromJson
関数にて、実際にこれを用いてデフォルト値を含む関数呼び出しを実装している様子が見られます。