Kotlin リフレクション

  • 2
    いいね
  • 0
    コメント

自己紹介

iOS(iOSの方が長い)とAndroidアプリのエンジニア。
ここ半年はAndroidメイン。
Oisixで働いています。


概要

Javaなど、各言語で備えるリフレクション機能のKotlin版。
クラス構造(プロパティなど)を読み取ったり書き換えたりする機能。KClass(クラス参照)、KProperty(プロパティ参照)を利用する。

https://kotlinlang.org/docs/reference/reflection.html


Projectの設定

kotlin標準ライブラリ(kotlin-stdlib-xxx)には含まれていない(fileサイズ削減のため)のでapp/build.gradleに下記を追加。


dependencies {
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
}

※上記が無くてもリフレクションでビルドエラーにはならないが、値が返ってこなかったりエラーログが出力されたりした。


file size

kotlin-reflectのサイズは4.8KB。

スクリーンショット 2017-05-24 7.38.24.png


Class References

KotlinのClass参照はKClass型。

data class User(val firstName: String, var lastName: String,private val secretName: String)

val user = User("Test", "Debug", "Hoge")

val c:KClass<User> = User::class

.javaすると、Java class referenceが取れる

val j:Class<User> = User::class

KotlinのClassリファレンスからプロパティ取得

val kClass = User::class
kClass.memberProperties.forEach { p ->
   Log.d(TAG, p.name + ":" + p.get(user))
}

Userクラスにprivateなpropertyがあると、IllegalCallableAccessExceptionがthrowされる

val kClass = User::class
kClass.memberProperties.forEach { p ->
    try {
        Log.d(TAG, p.name + ":" + p.get(user))
    } catch (e: IllegalCallableAccessException) {
        Log.d(TAG, p.name + " is private property.")
    }
}

isAccessible = trueにするとprivateなpropertyも参照できる(javaと同じ)

val kClass = User::class
kClass.memberProperties.forEach { p ->
    try {
         p.isAccessible = true//これを設定すると、privateなpropertyも参照できる
        Log.d(TAG, p.name + ":" + p.get(user))
    } catch (e:Throwable) {
        e.printStackTrace()
    }
}

JavaのClassリファレンスからもプロパティ取得できる

val javaClass = User::class.java
javaClass.declaredFields.forEach { field ->
    field.isAccessible = true
    Log.d(TAG, field.name + ":" + field.get(user))
}

Property References

クラス名::プロパティ名がProperty References。
KProperty型(Interface)


val firstName = User::firstName.get(user)
val javaGetter = User::firstName.javaGetter//ゲッターメソッド(Method型)。public final getFirstName() 
val javaField = User::firstName.javaField//Field(Field型)。private final firstName 

User::lastName.set(user, "lastNameDebug")//これで値できるセット

val sercretName = User::sercretName.get(user)//コンパイルエラー。private fieldはこの方法では参照できない



実際にプロパティを取得するときは、下記pがKProperty1<プロパティを持つクラス, プロパティの型>(KPropertyから派生)を実装したKProperty1Implクラス(内部で自動生成されるクラス)になっている。
Mutable(変更可能)なプロパティはKMutableProperty1Implクラスになっている。

val kClass = User::class
kClass.memberProperties.forEach { p:KProperty1<User, *> ->
    try {
        p.isAccessible = true//これを設定すると、privateなpropertyも参照できる
        Log.d(TAG, p.name + ":" + p.get(user))
    } catch (e:Throwable) {
        e.printStackTrace()
    }
}

KPropertyの派生インターフェースは下記3種類が存在する。
また、各インターフェースはMutable版(KMutableProperty1)が存在している

  • KProperty0:javaクラスのstatic変数を取得するときに利用
  • KProperty1:先ほどの通り。メンバーのプロパティ取得時に利用
  • KProperty2:拡張プロパティ取得時に利用

まとめ

  • プロパティ周りはJavaと同じような感じでアクセスできそう。
  • デバッグ時のクラス内容出力などに利用できそう
  • Function Referencesなど、他にもリフレクションは機能はいろいろありそう
  • apkサイズ大きくするほどかは考えた方が良いかも

ご静聴ありがとうございました