Kotlin リフレクション

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

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