6
2

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 1 year has passed since last update.

リフレクションでKotlinの要素を取得する【Kotlinリフレクション編】

Posted at

Kotlinリフレクションを使用したKotlin要素の取得方法についてまとめました。
リフレクションは、privateなクラスやメソッドにアクセスするために便利な機能です。
前回のJavaリフレクション編に引き続き、今回もAndroidの単体テスト例を用いてまとめています。

尚、ご意見や筆者の認識の誤り等がありましたら、お気軽にコメントをお願いいたします。

まとめ

JavaリフレクションとKotlinリフレクション

今回、Kotlinリフレクションでは以下の要素が取得できると分かりました。

  • public クラス
  • public / private プロパティ
  • public / private メソッド
  • public / private コンストラクタ

そして、privateクラスのみ取得ができませんでした。
そのため、基本的にはKotlinリフレクションを使用し、privateクラスを取得する時のみJavaリフレクションを使用すると良いのではと思います。

取得する要素と使用するメソッドのまとめ

  • public クラス
    • ::classで取得
  • public / private プロパティ
    • publicプロパティの取得はクラス名::プロパティ名
    • privateプロパティの取得はKClass型.memberPropertiesisAccessible = true指定
  • public / private メソッド
    •  publicメソッドの取得はクラス名::メソッド名
    •  privateメソッドの取得はKClass型.declaredMemberFunctionsisAccessible = true指定
  • public / private コンストラクタ
    • kClass.constructorsで取得、privateコンストラクタではisAccessible = true指定

使用するための準備

【確認環境】
Kotlin version:1.5.20
kotlin:kotlin-reflect:1.5.20

Kotlinリフレクションを使うためには、パッケージの追加が必要です。
AndroidStudioで新規プロジェクトを作成する場合はデフォルトで追加されていますが、万が一リフレクションが使えない!となった場合は、以下の依存関係を確認をしましょう。

build.gradle(app)
dependencies {
    implementation 'org.jetbrains.kotlin:kotlin-reflect:1.5.20'
}

Kotlinリフレクション使用例

今回も以下のソースコードをもとに、Kotlinリフレクションを使用して要素を取得、テストを実施します。
尚、今回使用する以下のコードはリフレクションの事例をまとめるために使用するもので、不完全な部分を含みますのでご了承ください。

//Curryクラス (publicコンストラクタを持つクラス)
class Curry(potato: Int) {
   val publicP = "publicPotato: ${potato}kg"
   private val privateP = "privatePotato: ${potato}kg"
   var ppp  = "ppp"

   fun publicM(carrot: Int): String{
       return "publicCarrot: ${carrot}kg"
   }

   private fun privateM(carrot: Int): String{
       return "privateCarrot: ${carrot}kg"
   }

   private class MakeCurry(onion: String, val meat: Int){
       val nestPublicP = "nestPublic-$onion"
       private val nestPrivateP = "nestPrivate-$onion"

       fun nestPublicM(water: Int): String {
           return "nestPublicMeat: ${meat+water}kg"
       }

       private fun nestPrivateM(water: Int): String {
           return "nestPrivateMeat: ${meat+water}kg"
       }
   }
}

//Stewクラス (privateコンストラクタを持つクラス)
class Stew private constructor(val potato: Int, val carrot: Int) {
   val publicP = "publicPotato: ${potato}kg"

   private fun privateM(onion: Int): String {
      return "${publicP}, privateNetWeight: ${potato+carrot+onion}kg"
   }
}

publicクラスの要素

publicプロパティの取得

publicプロパティは、 ::オペレーターで取得可能です。
::オペレーターは、内部にてKProperty 型のプロパティオブジェクトとして処理されます。
このKPropertyはkotlin.reflectionに含まれるクラスで、その名の通りプロパティを表します。
余談ではありますが、kotlin.reflectionには他にも、KClassクラス、KFunctionクラス、KCallableクラスなどがあります。

今回publicプロパティを取得するために使用するメソッドは、以下の2つです。

  • クラス名::プロパティ名
    KClassを取得します。
  • KProperty.get(インスタンス)
    指定したインスタンス時のプロパティの値を取得するためのメソッドです。
    @Test
    fun getPublicP() {
        // テストのため通常通りプロパティを取得する(リフレクションではない)
        val actual = Curry(2).publicP
        //①KProperty1を取得
        val kProperty = Curry::publicP
        //②プロパティの値を取得
        val expected = kProperty.get(Curry(2))
        //検証
        assertEquals(expected, actual)
    }

また、Javaリフレクション使用時は定数(val)でも値をsetできましたが、kotlinリフレクションではsetが使えるのは変数(var)の場合のみのようです。
変数のset時には、以下のメソッドを使用します。

  • KMutableProperty.set(インスタンス, setしたい値)
    setメソッドは、変数として宣言されたプロパティを表すためのKMutablePropertyクラスに実装されています。
    また、::プロパティ(変数)名を指定すると自動でKMutablePropertyが取得されます。
    //①変数を取得
    val kMutableProperty1 = Curry::ppp
    //②setを実装
    kMutableProperty1.set(Curry(2), "aaa")

privateプロパティの取得

privateプロパティの場合、::オペレーターを使用して直接KPropertyを取得することはできません。
そのため、直接KPropertyを取得するのではなく、KClassを経由してプロパティを取得します。

privateプロパティを取得するために使用するリフレクション関係メソッドは以下の5つです。

  • クラス名::class
    KClassを取得します。
  • KClass.memberProperties
    当該クラスとそのスーパークラスで宣言されている非拡張プロパティを取得するメソッドです。
    戻り値はCollection<KProperty1<KClassのクラス名, *>>型です。
  • KProperty.name
    プロパティの名前を返すメソッドです。
    今回はこれを用いて、プロパティの判別を行います。
  • isAccessible
    取得した要素へのアクセスを許可するためのメソッドです。
    = trueを設定することで、privateな要素へのアクセスが可能になります。
    このisAccessibleメソッドは、KPropertyとKFunctionのスーパータイプにあたるKCallableに実装されています。
  • KProperty型.get(インスタンス)
    指定したインスタンスの時の、プロパティの値を取得するメソッドです。
@Test
    fun getPrivateP() {
        //①KClassを取得
        val kClass = Curry::class
        //②プロパティ配列を取得(今回はprivatePとpublicPを持つ配列が取得される)
        val kPropertyC = kClass.memberProperties
        //③配列内の要素をforEach文で取り出し、名前で条件分岐する
        kPropertyC.forEach { //it:KProperty1<Curry, *>
            if(it.name == "privateP") {
                //④アクセスを許可
                it.isAccessible = true
                //⑤プロパティの値を取得
                val actual = it.get(Curry(2))
                //検証
                assertEquals("privatePotato: 2kg", actual)
            }
        }
    }

publicメソッドの取得

publicメソッドを取得するために今回使用するメソッドは以下の2つです。

  • ::メソッド名
    KFunctionを取得します。
  • call(インスタンス, メソッドの引数1, 引数2, …)
    指定された引数のリストを使用して、結果を返すメソッドです。
    以下の例ではkFunctionに作用し、メソッドが実行された結果を返します。
    このcallメソッドは、KPropertyとKFunctionのスーパータイプにあたるKCallableに実装されています。
    @Test
    fun publicMTest() {
        //テストのため通常通りメソッドを実行(リフレクションではない)
        val actual = Curry(2).publicM(1)
        //①KFunctionを取得
        val kFunction = Curry::publicM
        //②メソッドを実行
        val expected = kFunction.call(Curry(2), 1) 
        //検証
        assertEquals(expected, actual)
    }

privateメソッドの取得

privateメソッドの場合、::オペレーターを使用して直接KFunctionを取得することはできません。そのため、KClassを経由してプロパティを取得します。
今回privateメソッドを取得するために使用するメソッドは以下の4つです。

  • クラス名::class
    KClassを取得します。
  • KClass.declaredMemberFunctions
    クラス内に設置した関数を取得するためのメソッドです。
    戻り値は、 Collection>型です。
  • isAccessible
    取得した要素へのアクセスを許可するためのメソッドです。
    = trueを設定することで、privateな要素へのアクセスが可能になります。
    このisAccessibleメソッドは、KPropertyとKFunctionのスーパータイプにあたるKCallableに実装されています。
  • call(インスタンス, メソッドの引数1, 引数2, …)
    指定された引数のリストを使用して、結果を返すメソッドです。
    以下の例ではitであるKFunction<*>に作用し、メソッドが実行された結果を返します。
    このcallメソッドも、KCallableに実装されています。
@Test
    fun privateMTest() {
        //①KClassを取得
        val kClass = Curry::class
        //②関数配列を取得
        val kFunction = kClass.declaredMemberFunctions
        //③配列内の要素をforEach文で取り出し、名前で条件分岐する
        kFunction.forEach { //it: KFunction<*>
            if (it.name == "privateM"){
                //④アクセスを許可
                it.isAccessible = true
                //⑤メソッドを実行し、戻り値を取得
                val actual = it.call(Curry(2), 3)
                //検証
                assertEquals("privateCarrot: 3kg", actual)
            }
        }
    }

privateクラスの要素

今回確認した範囲では、privateクラスは取得できませんでした。
そのため、privateクラスの要素を取得する場合にはJavaリフレクションを使用することになると考えています。
Javaリフレクションを使用したprivateクラスの取得方法

privateコンストラクタを持つpublicクラスの要素

publicプロパティの取得

privateコンストラクタを持つクラスのpublicプロパティを取得するために、以下の5つのメソッドを使用します。

  • クラス名::class
    KClassを取得します。
  • KClass.constructors
    当該クラスで宣言された全てのコンストラクタを取得するためのメソッドです。
    戻り値は、Collection>です。
  • isAccessible
    取得した要素へのアクセスを許可するためのメソッドです。
    = trueを設定することで、privateな要素へのアクセスが可能になります。
    このisAccessibleメソッドは、KPropertyとKFunctionのスーパータイプにあたるKCallableに実装されています。
  • call(インスタンス, メソッドの引数1, 引数2, …)
    指定された引数のリストを使用して、結果を返すメソッドです。
    以下の例ではitであるKFunctionに作用し、インスタンスが生成されます。
    このcallメソッドも、KCallableに実装されています。
  • インスタンス::プロパティ名.get()
    プロパティの値を取得するメソッドです。
@Test
    fun getPublicP() {
        //①KClassを取得
        val kClass = Stew::class
        //②コンストラクタ配列を取得
        val const = kClass.constructors
        //③配列内の要素をforEach文で取り出す
        const.forEach { //it: KFunction<Stew>
            //④アクセスを許可する
            it.isAccessible = true
            //⑤インスタンスを生成し、プロパティの値を取得する
            val actual = it.call(1, 2)::publicP.get()
            //検証
            assertEquals("publicPotato: 1kg", actual)
        }
    }

privateメソッドの取得

流れとしては、

@Test
    fun privateM() {
        //①KClassを取得
        val kClass = Stew::class
        //②コンストラクタ配列の1つ目を取得(constはKFunction<Stew>)
        val const = kClass.constructors.first()
        //③コンストラクタへのアクセスを許可
        const.isAccessible = true
        //⑤インスタンスを生成する
        val kFunction = const.call(1, 2)
        //⑥関数配列を取得し、配列内の要素をforEach文で取り出す
        kFunction::class.declaredMemberFunctions.forEach { it -> //it: KFunction<*>
            //⑦名前で条件分岐する
            if (it.name == "privateM") {
                //⑧メソッドへのアクセスを許可する
                it.isAccessible = true
                //⑨メソッドを実行する
                val actual = it.call(kFunction, 4)
                //検証
                assertEquals("publicPotato: 1kg, privateNetWeight: 7kg", actual)
            }
        }
    }

参考

Reflection | Kotlin
Qiita:Kotlin リフレクション
hatenablog:Kotlinのdata classのpropertyをreflectionで更新する - abcdefg.....
hatenablog:Kotlinのリフレクション(protected/privateメソッド呼び出し)

6
2
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
6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?