Kotlin
Réflexion
Recherche…
Introduction
La réflexion est la capacité d'un langage à inspecter le code à l'exécution au lieu de la compilation.
Remarques
Reflection est un mécanisme d'introspection des constructions de langage (classes et fonctions) au moment de l'exécution.
Lors du ciblage de la plate-forme JVM, les fonctionnalités de réflexion à l'exécution sont distribuées dans un kotlin-reflect.jar
JAR distinct: kotlin-reflect.jar
. Ceci est fait pour réduire la taille de l'exécution, réduire les fonctionnalités inutilisées et permettre de cibler d'autres plateformes (comme JS).
Référencement d'une classe
Pour obtenir une référence à un objet KClass
représentant des doubles KClass
utilisation de classe:
val c1 = String::class
val c2 = MyClass::class
Référencement d'une fonction
Les fonctions sont des citoyens de première classe à Kotlin. Vous pouvez obtenir une référence à l'aide de deux points et la transmettre à une autre fonction:
fun isPositive(x: Int) = x > 0
val numbers = listOf(-2, -1, 0, 1, 2)
println(numbers.filter(::isPositive)) // [1, 2]
Interaction avec la réflexion Java
Pour obtenir un objet de Class
Java à partir du KClass
de Kotlin, utilisez la propriété d’extension .java
:
val stringKClass: KClass<String> = String::class
val c1: Class<String> = stringKClass.java
val c2: Class<MyClass> = MyClass::class.java
Le dernier exemple sera optimisé par le compilateur pour ne pas allouer une instance KClass
intermédiaire.
Obtenir des valeurs de toutes les propriétés d'une classe
Étant donné la classe Example
qui BaseExample
classe BaseExample
avec certaines propriétés:
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"
}
On peut s'emparer de toutes les propriétés d'une classe:
val example = Example(field1 = "abc", field2 = 1, baseField = "someText")
example::class.memberProperties.forEach { member ->
println("${member.name} -> ${member.get(example)}")
}
L'exécution de ce code entraînera la levée d'une exception. La propriété private val privateField
est déclarée privée et l'appel de member.get(example)
ne réussira pas. Une façon de gérer cela pour filtrer les propriétés privées. Pour ce faire, nous devons vérifier le modificateur de visibilité du getter Java d'une propriété. En cas de valeur private val
le getter n'existe pas, nous pouvons donc supposer un accès privé.
La fonction d'assistance et son utilisation peuvent ressembler à ceci:
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)}")
}
Une autre approche consiste à rendre les propriétés privées accessibles en utilisant la réflexion:
example::class.memberProperties.forEach { member ->
member.isAccessible = true
println("${member.name} -> ${member.get(example)}")
}
Définition des valeurs de toutes les propriétés d'une classe
A titre d'exemple, nous voulons définir toutes les propriétés de chaîne d'une classe d'échantillon
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
}
}
}
Obtenir des propriétés modifiables s'appuie sur l'obtention de toutes les propriétés, en filtrant les propriétés mutables par type. Nous devons également vérifier la visibilité, car la lecture des propriétés privées entraîne une exception d'exécution.
val instance = TestClass()
TestClass::class.memberProperties
.filter{ prop.visibility == KVisibility.PUBLIC }
.filterIsInstance<KMutableProperty<*>>()
.forEach { prop ->
System.out.println("${prop.name} -> ${prop.get(instance)")
}
Pour définir toutes les propriétés de String
sur "Our Value"
nous pouvons également filtrer par le type de retour. Étant donné que Kotlin est basé sur la machine virtuelle Java, Type Erasure est activé et les propriétés renvoyant des types génériques tels que List<String>
seront donc identiques à List<Any>
. Malheureusement, la réflexion n'est pas une solution miracle et il n'y a pas de moyen sensé d'éviter cela. Vous devez donc faire attention à vos cas d'utilisation.
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")
}