Ricerca…


introduzione

Reflection è la capacità di una lingua di ispezionare il codice in fase di runtime anziché la compilazione del tempo.

Osservazioni

Reflection è un meccanismo per analizzare i costrutti del linguaggio (classi e funzioni) al runtime.

Durante il targeting della piattaforma JVM, le funzioni di riflessione runtime sono distribuite in JAR separati: kotlin-reflect.jar . Questo viene fatto per ridurre le dimensioni del runtime, tagliare le funzionalità non utilizzate e rendere possibile il targeting di altre piattaforme (come JS).

Fare riferimento a una classe

Per ottenere un riferimento a un oggetto KClass che rappresenta alcuni doppi punti di classe:

val c1 = String::class
val c2 = MyClass::class

Riferimento a una funzione

Le funzioni sono cittadini di prima classe a Kotlin. È possibile ottenere un riferimento su di esso utilizzando doppio punto e quindi passarlo a un'altra funzione:

fun isPositive(x: Int) = x > 0

val numbers = listOf(-2, -1, 0, 1, 2)
println(numbers.filter(::isPositive)) // [1, 2]

Interoperando con la riflessione di Java

Per ottenere un oggetto di Class Java da Kotlin, KClass usa la proprietà di estensione .java :

val stringKClass: KClass<String> = String::class
val c1: Class<String> = stringKClass.java

val c2: Class<MyClass> = MyClass::class.java

L'ultimo esempio verrà ottimizzato dal compilatore per non allocare un'istanza di KClass intermedia.

Ottenere valori di tutte le proprietà di una classe

Dato Example classe che estende la classe BaseExample con alcune proprietà:

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

Si possono ottenere tutte le proprietà di una classe:

val example = Example(field1 = "abc", field2 = 1, baseField = "someText")

example::class.memberProperties.forEach { member ->
    println("${member.name} -> ${member.get(example)}")
}

L'esecuzione di questo codice causerà il lancio di un'eccezione. La proprietà private val privateField è dichiarata privata e la chiamata member.get(example) su di essa non avrà successo. Un modo per gestirlo per filtrare le proprietà private. Per farlo dobbiamo controllare il modificatore di visibilità del getter Java di una proprietà. Nel caso di private val il getter non esiste quindi possiamo assumere un accesso privato.

La funzione di supporto e il suo utilizzo potrebbero assomigliare a questo:

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

Un altro approccio è rendere accessibili le proprietà private utilizzando la riflessione:

example::class.memberProperties.forEach { member ->
    member.isAccessible = true
    println("${member.name} -> ${member.get(example)}")
}

Impostazione dei valori di tutte le proprietà di una classe

Ad esempio vogliamo impostare tutte le proprietà stringa di una classe di esempio

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

Ottenere le proprietà mutabili si basa sull'acquisizione di tutte le proprietà, filtrando le proprietà mutabili per tipo. Dobbiamo anche controllare la visibilità, poiché la lettura delle proprietà private risulta in un'eccezione di run-time.

val instance = TestClass()
TestClass::class.memberProperties
        .filter{ prop.visibility == KVisibility.PUBLIC }
        .filterIsInstance<KMutableProperty<*>>()
        .forEach { prop ->
            System.out.println("${prop.name} -> ${prop.get(instance)")
        }

Per impostare tutte le proprietà String su "Our Value" possiamo anche filtrare in base al tipo restituito. Poiché Kotlin è basato su Java VM, Type Erasure è in effetti, e quindi le proprietà che restituiscono tipi generici come List<String> saranno uguali a List<Any> . Purtroppo la riflessione non è una pallottola d'oro e non esiste un modo ragionevole per evitarlo, quindi è necessario prestare attenzione ai casi d'uso.

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
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow