元の記事 → 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("")
}