Kotlin
reflection

Kotlin リフレクション

More than 1 year has passed since last update.


自己紹介

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サイズ大きくするほどかは考えた方が良いかも



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