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型.memberProperties
、isAccessible = true
指定
- publicプロパティの取得は
- public / private メソッド
- publicメソッドの取得は
クラス名::メソッド名
- privateメソッドの取得は
KClass型.declaredMemberFunctions
、isAccessible = true
指定
- publicメソッドの取得は
- public / private コンストラクタ
-
kClass.constructors
で取得、privateコンストラクタではisAccessible = true
指定
-
使用するための準備
【確認環境】
Kotlin version:1.5.20
kotlin:kotlin-reflect:1.5.20
Kotlinリフレクションを使うためには、パッケージの追加が必要です。
AndroidStudioで新規プロジェクトを作成する場合はデフォルトで追加されていますが、万が一リフレクションが使えない!となった場合は、以下の依存関係を確認をしましょう。
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メソッドの取得
流れとしては、
- privateコンストラクタを持つクラスのpublicプロパティの取得の前半(配列内の要素をforEach文で取り出すまで)と
-
publicクラスのprivateメソッドの取得の後半(関数配列の取得以降)
を組み合わせて取得できます。
ただし、今回の例ではコンストラクタは1つしか実装していないため、②にてコンストラクタ配列を取得後は1つ目の要素のみ取得しています。
@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メソッド呼び出し)