Kotlin
Reflexion
Suche…
Einführung
Reflection ist die Fähigkeit einer Sprache, zur Laufzeit anstelle der Kompilierzeit Code zu prüfen.
Bemerkungen
Reflection ist ein Mechanismus, um Sprachkonstrukte (Klassen und Funktionen) zur Laufzeit zu untersuchen.
Beim Targeting auf die JVM-Plattform werden Laufzeitreflexionsfunktionen in separaten JAR kotlin-reflect.jar
verteilt: kotlin-reflect.jar
. Dies geschieht, um die Laufzeitgröße zu reduzieren, nicht benötigte Funktionen zu reduzieren und es möglich zu machen, auf andere Plattformen (wie JS) zu zielen.
Eine Klasse referenzieren
Um einen Verweis auf ein KClass
Objekt zu erhalten, das eine Klasse darstellt, verwenden Sie Doppelpunkte:
val c1 = String::class
val c2 = MyClass::class
Funktion referenzieren
Funktionen sind erstklassige Bürger in Kotlin. Sie können einen Verweis darauf mit Doppelpunkten erhalten und dann an eine andere Funktion übergeben:
fun isPositive(x: Int) = x > 0
val numbers = listOf(-2, -1, 0, 1, 2)
println(numbers.filter(::isPositive)) // [1, 2]
Interaktion mit Java Reflection
Um ein Java- Class
Objekt von KClass
verwenden Sie die Erweiterungseigenschaft .java
:
val stringKClass: KClass<String> = String::class
val c1: Class<String> = stringKClass.java
val c2: Class<MyClass> = MyClass::class.java
Das letztere Beispiel wird vom Compiler KClass
optimiert, dass keine KClass
Zwischeninstanz KClass
.
Werte aller Eigenschaften einer Klasse abrufen
BaseExample
Example
, die die BaseExample
Klasse mit einigen Eigenschaften erweitert:
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"
}
Man kann alle Eigenschaften einer Klasse erhalten:
val example = Example(field1 = "abc", field2 = 1, baseField = "someText")
example::class.memberProperties.forEach { member ->
println("${member.name} -> ${member.get(example)}")
}
Wenn Sie diesen Code ausführen, wird eine Ausnahme ausgelöst. Die Eigenschaft private val privateField
wird als privat deklariert, und der Aufruf von member.get(example)
wird nicht erfolgreich durchgeführt. Eine Möglichkeit, damit umzugehen, um private Eigenschaften herauszufiltern. Dazu müssen wir den Sichtbarkeits-Modifizierer des Java-Getters einer Eigenschaft überprüfen. Im Falle von private val
der Getter nicht, so dass wir einen privaten Zugriff annehmen können.
Die Hilfsfunktion und ihre Verwendung könnte folgendermaßen aussehen:
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)}")
}
Ein anderer Ansatz besteht darin, private Immobilien über Reflexion zugänglich zu machen:
example::class.memberProperties.forEach { member ->
member.isAccessible = true
println("${member.name} -> ${member.get(example)}")
}
Einstellungswerte aller Eigenschaften einer Klasse
Als Beispiel möchten wir alle String-Eigenschaften einer Beispielklasse festlegen
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
}
}
}
Das Abrufen von veränderbaren Eigenschaften baut auf dem Abrufen aller Eigenschaften auf, wobei die veränderbaren Eigenschaften nach Typ gefiltert werden. Wir müssen auch die Sichtbarkeit prüfen, da das Lesen privater Eigenschaften zu Laufzeitausnahmen führt.
val instance = TestClass()
TestClass::class.memberProperties
.filter{ prop.visibility == KVisibility.PUBLIC }
.filterIsInstance<KMutableProperty<*>>()
.forEach { prop ->
System.out.println("${prop.name} -> ${prop.get(instance)")
}
Um alle String
Eigenschaften auf "Our Value"
, können wir zusätzlich nach dem Rückgabetyp filtern. Da Kotlin auf Java VM basiert, ist Type Erasure in Kraft und daher sind Eigenschaften, die generische Typen wie List<String>
, mit List<Any>
identisch. Leider ist das Nachdenken keine goldene Kugel, und es gibt keinen vernünftigen Weg, dies zu vermeiden. Daher müssen Sie in Ihren Anwendungsfällen aufpassen.
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")
}