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