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")
        }


Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow