Kotlinチートシート: reflection編

元の記事 → [Swift to Kotlinチートシート](https://qiita.com/alt_yamamoto/items/d545309093baa53a539e)

# Dependencyの設定

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

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

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

```Kotlin 
val foo = createInstance(Foo::class)

fun <T: Any> createInstance(type: KClass<T>): T {
    return type.java.newInstance()
}
```
TはAnyの制約をつけないとオプショナルも含まれてしまうため、`KClass<T>`が成立せずコンパイルエラーになる。

# メンバー変数・定数出力

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

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

# 文字列からenumを取得
ちょっとトリッキーで、ワーニングを抑制する必要がある。

```Kotlin
    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` を参照すると異様に時間がかかる現象が見られている。

```Kotlin
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を使う必要があるかも

```Kotlin
@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("")
}
```