Kotlin
Reflexion
Sök…
Introduktion
Reflektion är språkets förmåga att inspektera kod vid körning istället för att sammanställa tid.
Anmärkningar
Reflektion är en mekanism för att introspektera språkkonstruktioner (klasser och funktioner) under körtiden.
Vid inriktning på JVM-plattformen distribueras funktionen för reflektion av runtime i separata JAR: kotlin-reflect.jar
. Detta görs för att minska runtime-storleken, klippa oanvända funktioner och göra det möjligt att rikta in sig på en annan (som JS) plattformar.
Referera en klass
För att få en referens till ett KClass
objekt som representerar en klass använder du dubbla kolon:
val c1 = String::class
val c2 = MyClass::class
Referera till en funktion
Funktioner är förstklassiga medborgare i Kotlin. Du kan få en referens på den med dubbla kolon och sedan överföra den till en annan funktion:
fun isPositive(x: Int) = x > 0
val numbers = listOf(-2, -1, 0, 1, 2)
println(numbers.filter(::isPositive)) // [1, 2]
Samverkar med Java-reflektion
För att få en Javas Class
objekt från Kotlin s KClass
använda .java
förlängnings egenskapen:
val stringKClass: KClass<String> = String::class
val c1: Class<String> = stringKClass.java
val c2: Class<MyClass> = MyClass::class.java
Det senare exemplet kommer att optimeras av kompilatorn för att inte tilldela en mellanliggande KClass
instans.
Få värden på alla egenskaper i en klass
Givet Example
klass sträcker BaseExample
klass med vissa egenskaper:
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 kan få tag på alla egenskaper i en klass:
val example = Example(field1 = "abc", field2 = 1, baseField = "someText")
example::class.memberProperties.forEach { member ->
println("${member.name} -> ${member.get(example)}")
}
Att köra den här koden kommer att göra att ett undantag kastas. Property private val privateField
förklaras privat och att ringa member.get(example)
på det kommer inte att lyckas. Ett sätt att hantera detta med att filtrera bort privata fastigheter. För att göra det måste vi kontrollera synlighetsmodifieraren för en fastighets Java getter. Vid private val
finns inte getter så vi kan anta privat åtkomst.
Hjälparfunktionen och dess användning kan se ut så här:
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)}")
}
En annan metod är att göra privata fastigheter tillgängliga med reflektion:
example::class.memberProperties.forEach { member ->
member.isAccessible = true
println("${member.name} -> ${member.get(example)}")
}
Ställa in värden för alla egenskaper i en klass
Som exempel vill vi ställa in alla strängegenskaper i en provklass
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
}
}
}
Att få muterbara egenskaper bygger på att få alla egenskaper, filtrera muterbara egenskaper efter typ. Vi måste också kontrollera synligheten, eftersom läsning av privata egenskaper leder till undantag för körning.
val instance = TestClass()
TestClass::class.memberProperties
.filter{ prop.visibility == KVisibility.PUBLIC }
.filterIsInstance<KMutableProperty<*>>()
.forEach { prop ->
System.out.println("${prop.name} -> ${prop.get(instance)")
}
För att ställa in alla String
till "Our Value"
vi dessutom filtrera efter returtypen. Eftersom Kotlin är baserat på Java VM, är typ Erasure i kraft, och därför kommer egenskaper som returnerar generiska typer som List<String>
vara samma som List<Any>
. Tyvärr är reflektion inte en gyllene kula och det finns inget förnuftigt sätt att undvika detta, så du måste se upp i dina användningsfall.
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")
}