TL;DR
- インスタンスから取得した
KFunction
は引数のみで呼び出せる -
CompanionObject
から取得したKFunction
の場合、呼び出しには引数以外にCompanionObject
のインスタンスが必要1 -
KFunction
を継承したクラスでcall
/callBy
メソッドをよしなにやることで、CompanionObject
から取得したKFunction
もインスタンスから取得した関数同様に扱うことができる
課題になったこと
CompanionObject
から取得したKFunction
の場合、呼び出しには引数以外にCompanionObject
のインスタンスが必要です。
ここで、「コンストラクタ、static
関数、CompanionObject
に定義した関数の3種類から適切な関数を抽出し、実行する」プログラムを書いていた所、コンストラクタとstatic
関数はそのまま呼び出せるのに、CompanionObject
に定義した関数は扱いが異なってしまうという問題が発生しました。
ラムダでラップしてやれば扱いは同様にできますが、ラップする手間や無駄な生成コストが嫌だったので、CompanionObject
の定義から取得したKFunction
をインスタンス無しで呼び出せるKFunction
のように振る舞わせる方法を探しました。
解決方法
以下の通り、KFunction
を継承したクラスでインスタンス関連の取り扱いを隠ぺいすることで、課題が解決できます。
import kotlin.reflect.KFunction
import kotlin.reflect.KParameter
import kotlin.reflect.full.instanceParameter
class CompanionKFunction<T>(
private val function: KFunction<T>, private val instance: Any
): KFunction<T> by function {
private val instanceParam by lazy { mapOf(function.instanceParameter!! to instance) }
override val parameters: List<KParameter> by lazy {
function.parameters.filter { it.kind != KParameter.Kind.INSTANCE }
}
override fun call(vararg args: Any?): T = function.call(instance, *args)
override fun callBy(args: Map<KParameter, Any?>): T = function.callBy(instanceParam + args)
}
val clazz: KClass<*> = // クラス取得
val companionObjectInstance: Any = clazz.companionObjectInstance!! // コンパニオンオブジェクト取得
val companionFunction: KFunction<*> = companionObjectInstance.functions.first { /* 特定関数の取得 */ }
// インスタンス無しで呼び出せるように隠ぺい
val companionKFunction = CompanionKFunction(companionFunction, companionObjectInstance)
val returnValue = companionKFunction.call(/* インスタンス以外の引数 */)
使ったテクニック
Class Delegation
以下のように、$インターフェース by $移譲先インスタンス
としてやることで、インターフェースの実装を他クラスに移譲できます。
これにより、最低限の手間でインターフェースを実装することができます。
class CompanionKFunction<T>(
private val function: KFunction<T>, private val instance: Any
): KFunction<T> by function {
可変長引数の展開
可変長引数を別の関数の可変長引数に渡す場合、*
(スプレッド演算子)で展開する必要が有ります。
override fun call(vararg args: Any?): T = function.call(instance, *args)
参考にさせて頂いた記事
- kotlin - How can I turn a KFunction without instance param to a KFunction with it? - Stack Overflow
- Kotlinの委譲(Delegation)について - Qiita
宣伝
Kotlin
向けModelMapper
的なライブラリを作っています。
是非使ってみて下さい。
-
Class::class.companionObject
/.companionObjectInstance
で取得した場合の話。method reference
を取得する場合はインスタンス無しで呼び出せる。 ↩