Kotlin
Reflectie
Zoeken…
Invoering
Reflectie is het vermogen van een taal om code tijdens runtime te inspecteren in plaats van te compileren.
Opmerkingen
Reflectie is een mechanisme om taalconstructies (klassen en functies) tijdens de runtime te onderzoeken.
Wanneer u JVM-platform target, worden runtime-reflectiefuncties verdeeld in afzonderlijke JAR: kotlin-reflect.jar
. Dit wordt gedaan om de runtime te verkleinen, ongebruikte functies te verminderen en het mogelijk te maken zich op andere (zoals JS) platforms te richten.
Verwijzen naar een klas
Gebruik dubbele dubbele punten om een verwijzing te krijgen naar een KClass
object dat een klasse vertegenwoordigt.
val c1 = String::class
val c2 = MyClass::class
Verwijzen naar een functie
Functies zijn eersteklas burgers in Kotlin. U kunt er een referentie op krijgen met dubbele dubbele punten en deze vervolgens doorgeven aan een andere functie:
fun isPositive(x: Int) = x > 0
val numbers = listOf(-2, -1, 0, 1, 2)
println(numbers.filter(::isPositive)) // [1, 2]
Interactie met Java-reflectie
Om een Java's Class
object van Kotlin's KClass
gebruikt u de .java
extensie:
val stringKClass: KClass<String> = String::class
val c1: Class<String> = stringKClass.java
val c2: Class<MyClass> = MyClass::class.java
Het laatste voorbeeld wordt door de compiler geoptimaliseerd om geen tussenliggende KClass
instantie toe te KClass
.
Waarden ophalen van alle eigenschappen van een klasse
Gegeven Example
klasse BaseExample
klasse met enkele eigenschappen uitgebreid:
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"
}
Men kan alle eigenschappen van een klasse te pakken krijgen:
val example = Example(field1 = "abc", field2 = 1, baseField = "someText")
example::class.memberProperties.forEach { member ->
println("${member.name} -> ${member.get(example)}")
}
Als u deze code uitvoert, wordt een uitzondering gegenereerd. Property private val privateField
wordt privé verklaard en aanroepend member.get(example)
daarop zal niet slagen. Een manier om dit aan te pakken is om privé-eigendommen uit te filteren. Om dat te doen moeten we de zichtbaarheidsmodifier van de Java-getter van een eigenschap controleren. In het geval van private val
de getter niet, dus kunnen we van particuliere toegang uitgaan.
De helpfunctie en het gebruik ervan kunnen er zo uitzien:
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)}")
}
Een andere benadering is om privé-eigendommen toegankelijk te maken met behulp van reflectie:
example::class.memberProperties.forEach { member ->
member.isAccessible = true
println("${member.name} -> ${member.get(example)}")
}
Waarden instellen voor alle eigenschappen van een klasse
Als voorbeeld willen we alle stringeigenschappen van een voorbeeldklasse instellen
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
}
}
}
Het verkrijgen van veranderlijke eigenschappen is gebaseerd op het verkrijgen van alle eigenschappen en filtert veranderlijke eigenschappen op type. We moeten ook de zichtbaarheid controleren, omdat het lezen van privé-eigenschappen leidt tot een uitzondering voor runtime.
val instance = TestClass()
TestClass::class.memberProperties
.filter{ prop.visibility == KVisibility.PUBLIC }
.filterIsInstance<KMutableProperty<*>>()
.forEach { prop ->
System.out.println("${prop.name} -> ${prop.get(instance)")
}
Om alle String
eigenschappen in te stellen op "Our Value"
, kunnen we bovendien filteren op het retourtype. Aangezien Kotlin is gebaseerd op Java VM, is Type Erasure van kracht en derhalve zullen eigenschappen die generieke typen zoals List<String>
retourneren hetzelfde zijn als List<Any>
. Spijtig genoeg is reflectie geen gouden kogel en er is geen verstandige manier om dit te voorkomen, dus je moet uitkijken in je use-cases.
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")
}