LoginSignup
13
8

More than 5 years have passed since last update.

Kotlinチートシート: reflection編

Last updated at Posted at 2018-01-29

元の記事 → Swift to Kotlinチートシート

Dependencyの設定

Kotlinでリフレクションを使うには、Dependencyを追加する必要がある。

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

クラスオブジェクトからインスタンスの作成

val foo = createInstance(Foo::class)

fun <T: Any> createInstance(type: KClass<T>): T {
    return type.java.newInstance()
}

TはAnyの制約をつけないとオプショナルも含まれてしまうため、KClass<T>が成立せずコンパイルエラーになる。

メンバー変数・定数出力

fun <T: Any> print(type: KClass<T>, obj: T) {

    type.memberProperties.forEach {
        println("${it.name}: ${it.get(obj)}")
    }
}

文字列からenumを取得

ちょっとトリッキーで、ワーニングを抑制する必要がある。

    fun <T: Enum<*>>getEnumValue(type: KClass<T>, name: String?) : T? {
        try {
            @Suppress("UPPER_BOUND_VIOLATED", "UNCHECKED_CAST")
            val result = java.lang.Enum.valueOf<Enum<*>>(type.java as Class<Enum<*>>, name)
            return result as? T
        } catch (e: IllegalArgumentException) {
            return null
        }
    }

Androidで memberProperties が異様に遅い

Androidで memberProperties を参照すると異様に時間がかかる現象が見られている。

class PlainClass {}

fun test() {
    print(PlainClass::class)
    print(View::class)
    print(ConstraintLayout::class)
}

fun print(kClass: KClass<*>) {
    val before = System.currentTimeMillis()
    kClass.memberProperties
    val after = System.currentTimeMillis()
    Log.d("TEST", "${kClass.simpleName} ${after - before} ms")
}

Xperia Z3 (Android5)で上記を試した結果。

D/TEST: PlainClass 1226 ms
D/TEST: View 6154 ms
D/TEST: ConstraintLayout 9443 ms

他の端末でも試したが、速度の違いはあれやはり遅かった。
Kotlinを使わずJavaでfieldを参照しても遅いようなので、この問題はどうしようもないかもしれない。

値のセット、アノテーション

@IBOutlet がついたプロパティーにViewからスネークケースにしたidの子Viewを探し出してセットする例。
※ただし、上記のmemberProperties遅い問題があるので実用レベルではない。同じようなことを実用レベルでやろうとすると、ButterKnifeみたいにAPTを使う必要があるかも

@Target(AnnotationTarget.PROPERTY)
@MustBeDocumented
annotation class IBOutlet

open class ViewController() {
    lateinit var rootView: View

    fun bindIBOutlets() {
        this::class.memberProperties.forEach {
            checkProperty(it)
        }
    }

    private fun checkProperty(property: KProperty1<out ViewController, Any?>) {
        if (property.findAnnotation<IBOutlet>() == null) {
            return
        }

        if (property !is KMutableProperty1) {
            Log.w("IBOutlet", "IBOutlet property [${property.name}] is immutable !!")
            return
        }

        try {
            val idString = property.name.toSnakeCase()
            val activity = MainActivity.instance
            val viewId = activity.resources.getIdentifier(idString, "id", activity.packageName)
            val view = rootView.findViewById<View>(viewId)
            if (view != null) {
                property.setter.call(this, view)
            } else {
                Log.w("IBOutlet", "Can't find view id [${idString}] !!")
            }
        } catch (e: IllegalCallableAccessException) {
            Log.w("IBOutlet", "Can't access IBOutlet property [${property.name}] !!")
        }
    }
}

fun String.toSnakeCase(): String {
    var canGoNextBlock = false
    return map {
        val separator = if (canGoNextBlock && it.isUpperCase()) "_" else ""
        canGoNextBlock = it.isLowerCase()
        separator + it.toLowerCase()
    }.joinToString("")
}
13
8
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
13
8