サーチ…
前書き
リフレクションとは、コンパイル時ではなく実行時にコードを検査する言語の能力です。
備考
リフレクションは、実行時に言語構造(クラスおよび関数)をイントロスペクションするためのメカニズムです。
JVMプラットフォームを対象とする場合、実行時リフレクション機能は別々のJAR: kotlin-reflect.jar
ます。これは実行時のサイズを減らし、未使用の機能をカットし、別の(JSのような)プラットフォームをターゲットにすることを可能にするために行われます。
クラスの参照
いくつかのクラスを表すKClass
オブジェクトへの参照を取得するには、二重コロンを使用します。
val c1 = String::class
val c2 = MyClass::class
関数の参照
関数はKotlinの一流市民です。二重コロンを使用して参照を取得し、別の関数に渡すことができます。
fun isPositive(x: Int) = x > 0
val numbers = listOf(-2, -1, 0, 1, 2)
println(numbers.filter(::isPositive)) // [1, 2]
Javaリフレクションとの相互運用
KotlinのKClass
からJavaのClass
オブジェクトを取得するには、 .java
拡張プロパティを使用します。
val stringKClass: KClass<String> = String::class
val c1: Class<String> = stringKClass.java
val c2: Class<MyClass> = MyClass::class.java
後者の例は、中間のKClass
インスタンスを割り当てないようにコンパイラによって最適化されます。
クラスのすべてのプロパティの値を取得する
与えられたExample
拡張クラスBaseExample
いくつかのプロパティを持つクラスを:
open class BaseExample(val baseField: String)
class Example(val field1: String, val field2: Int, baseField: String):
BaseExample(baseField) {
val field3: String
get() = "Property without backing field"
val field4 by lazy { "Delegated value" }
private val privateField: String = "Private value"
}
クラスのすべてのプロパティを保持することができます:
val example = Example(field1 = "abc", field2 = 1, baseField = "someText")
example::class.memberProperties.forEach { member ->
println("${member.name} -> ${member.get(example)}")
}
このコードを実行すると、例外がスローされます。プロパティprivate val privateField
はprivateと宣言され、その上でmember.get(example)
を呼び出すと成功しません。これを処理してプライベートプロパティを除外する1つの方法。これを行うには、プロパティのJavaゲッターの可視性変更子をチェックする必要があります。 private val
の場合、getterは存在しないため、プライベートアクセスが可能です。
ヘルパー関数とその使用法は次のようになります:
fun isFieldAccessible(property: KProperty1<*, *>): Boolean {
return property.javaGetter?.modifiers?.let { !Modifier.isPrivate(it) } ?: false
}
val example = Example(field1 = "abc", field2 = 1, baseField = "someText")
example::class.memberProperties.filter { isFieldAccessible(it) }.forEach { member ->
println("${member.name} -> ${member.get(example)}")
}
別のアプローチは、リフレクションを使用してプライベートプロパティにアクセスできるようにすることです。
example::class.memberProperties.forEach { member ->
member.isAccessible = true
println("${member.name} -> ${member.get(example)}")
}
クラスのすべてのプロパティの値を設定する
例として、サンプルクラスのすべての文字列プロパティを設定する
class TestClass {
val readOnlyProperty: String
get() = "Read only!"
var readWriteString = "asd"
var readWriteInt = 23
var readWriteBackedStringProperty: String = ""
get() = field + '5'
set(value) { field = value + '5' }
var readWriteBackedIntProperty: Int = 0
get() = field + 1
set(value) { field = value - 1 }
var delegatedProperty: Int by TestDelegate()
private var privateProperty = "This should be private"
private class TestDelegate {
private var backingField = 3
operator fun getValue(thisRef: Any?, prop: KProperty<*>): Int {
return backingField
}
operator fun setValue(thisRef: Any?, prop: KProperty<*>, value: Int) {
backingField += value
}
}
}
変更可能なプロパティを取得するには、すべてのプロパティを取得し、変更可能なプロパティをタイプ別にフィルタリングします。プライベートプロパティを読み取ると実行時例外が発生するため、可視性もチェックする必要があります。
val instance = TestClass()
TestClass::class.memberProperties
.filter{ prop.visibility == KVisibility.PUBLIC }
.filterIsInstance<KMutableProperty<*>>()
.forEach { prop ->
System.out.println("${prop.name} -> ${prop.get(instance)")
}
すべてのString
プロパティを"Our Value"
に設定するには、戻り値の型でさらにフィルタリングできます。 KotlinはJava VMをベースにしているため、 タイプ消去が有効なので、 List<String>
などの汎用タイプを返すプロパティはList<Any>
と同じになります。残念ながら、反射は黄金の弾丸ではなく、これを避けるための賢明な方法はないので、あなたの使用事例で注意する必要があります。
val instance = TestClass()
TestClass::class.memberProperties
.filter{ prop.visibility == KVisibility.PUBLIC }
// We only want strings
.filter{ it.returnType.isSubtypeOf(String::class.starProjectedType) }
.filterIsInstance<KMutableProperty<*>>()
.forEach { prop ->
// Instead of printing the property we set it to some value
prop.setter.call(instance, "Our Value")
}