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


Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow