LoginSignup
0
1

More than 3 years have passed since last update.

【Kotlin】CompanionObjectの定義から取得したKFunctionをインスタンス無しで呼び出せるKFunctionのように振る舞わせる

Last updated at Posted at 2020-02-15

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向けModelMapper的なライブラリを作っています。
是非使ってみて下さい。


  1. Class::class.companionObject/.companionObjectInstanceで取得した場合の話。method referenceを取得する場合はインスタンス無しで呼び出せる。 

0
1
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
0
1