Il y a 6 ans -
Temps de lecture 6 minutes
Les KProperty2 ou la réflexion signée Kotlin
En parcourant la bibliothèque standard de Kotlin, section réflexion, on peut tomber sur des types tels que KProperty0, KProperty1 et KProperty2.
On comprend assez rapidement que les types KProperty*
sont des types qui représentent et permettent de manipuler, par réflexion, des propriétés i.e. des variables déclarées dans un package ou dans une classe. On comprend, en revanche, un peu plus difficilement pourquoi des propriétés peuvent être représentées par plusieurs types (et surtout par trois types différents). En Java, par exemple, l’équivalent d’une propriété – un champ – est toujours représenté par le type Field, qu’il soit statique ou non.
Cet article vous propose de découvrir ce que les différents types KProperty*
représentent. Nous commencerons donc par étudier les types KProperty0
et KProperty1
qui se révèlent, au final, assez simples. Nous pourrons ensuite mieux aborder le type KProperty2
, la configuration un peu inhabituelle qu’il représente et surtout en quoi il peut bien nous être utile.
Le type KProperty0
Comme le montre le code ci-dessous (lien gist), le type KProperty0
représente une variable immuable déclarée au niveau d’un package (sans aucun contexte).
[java gutter= »true » language= »wraplines »]package com.github.sergiords.kproperties
import kotlin.reflect.KProperty0
val name: String = "Bob"
fun main(args: Array<String>) {
val kProperty0: KProperty0<String> = ::name
val value: String = kProperty0() // value of the property "name"
println(value) // prints "Bob"
}[/java]
Dans cet exemple, pour obtenir une instance du type KProperty0
représentant la variable immuable name
on utilise l’opérateur « ::
» (ligne 9) appliqué à la variable elle-même. On voit que le type KProperty0
est paramétré par le type de la propriété qu’il représente (String
).
L’instance kProperty0
peut ensuite être utilisée pour récupérer la valeur de la variable (ligne 11).
Le type KProperty1
Le code ci-dessous (lien gist) met quant à lui en évidence le type KProperty1
qui représente une variable immuable déclarée au niveau d’une classe (dans le contexte d’un type User
).
[java gutter= »true » language= »wraplines »]package com.github.sergiords.kproperties
import kotlin.reflect.KProperty1
class User {
val name: String = "Bob"
}
fun main(args: Array<String>) {
val kProperty1: KProperty1<User, String> = User::name
val user: User = User()
val value: String = kProperty1(user) // value of the property "name" inside "user" instance
println(value) // prints "Bob"
}[/java]
Pour obtenir une instance du type KProperty1 représentant la variable immuable name
dans une instance du type User
on utilise l’opérateur « ::
» appliqué au type User
(ligne 13). On voit que le type KProperty1
est paramétré par le type contenant la variable (User
) et par le type de la propriété qu’il représente (String
). L’instance kProperty1
peut ensuite être utilisée pour récupérer la valeur de la variable immuable dans une instance du type User
(ligne 17).
Le type KProperty2
Nous venons de voir les types KProperty0
et KProperty1
, ils représentent respectivement une variable immuable déclarée au niveau d’un package (zéro contexte) et au niveau d’un type donné (un seul contexte, l’instance). Pour le type KProperty2
on suit le même raisonnement en ajoutant un contexte. Le type KProperty2
représente donc une variable immuable déclarée dans deux contextes. Comment est-ce possible ? Avec les extensions de propriétés, mises en évidence ci-dessous (lien gist).
[java gutter= »true » language= »wraplines »]package com.github.sergiords.kproperties
import kotlin.reflect.KProperty2
import kotlin.reflect.full.declaredMemberExtensionProperties
class Magic {
val String.magicLength: Int // add property magicLength to String type inside Magic class only
get() = this.length * 42
fun length(arg: String): Int = arg.magicLength
}
fun main(args: Array<String>) {
@Suppress("UNCHECKED_CAST")
val kProperty2: KProperty2<Magic, String, Int> = Magic::class.declaredMemberExtensionProperties.single() as KProperty2<Magic, String, Int>
val magic = Magic()
val abcdValue: Int = kProperty2(magic, "ABCD") // value of the property "magicLength" inside "ABCD" and "magic" instances
println(abcdValue) // since "ABCD".length = 4, prints 4 * 42 = 168
val abcdeValue: Int = magic.length("ABCDE") // value of the property "magicLength" inside "ABCDE" and "magic" instances
println(abcdeValue) // since "ABCDE".length = 5, prints 5 * 42 = 210
}
[/java]
La syntaxe un peu particulière des lignes 8 et 9 permet de rattacher une variable immuable magicLength
, au type String
, uniquement accessible dans une instance du type Magic
. Le this
de la ligne 9 représente bien l’instance du type String
qui reçoit cette nouvelle variable immuable. Cette syntaxe est une extension de propriété un peu particulière car elle est déclarée dans le contexte d’un autre type et c’est cette configuration de variable immuable que représente le type KProperty2
.
Pour obtenir une instance du type KProperty2
, on doit cette fois s’appuyer sur les extensions de propriétés déclarées au niveau du type Magic
(ligne 18). On voit bien que le type KProperty2
est paramétré par le type dans lequel il est déclaré (Magic
), par le type qui reçoit l’extension de propriété (String
) et par le type de la propriété qu’il représente (Int
). L’instance de kProperty2
peut ensuite être utilisée pour obtenir la valeur de la variable immuable magicLength
d’une chaîne « ABCD » dans une instance donnée du type Magic
(ligne 22).
Une utilisation plus standard de ces propriétés est la fonction Magic::length
qui utilise cette extension de propriété, mais cette fois, directement dans le type Magic
(déclaration à la ligne 11 et appel à la ligne 25).
Conclusion
L’objectif de cet article était avant tout de comprendre ce que les différents types KProperty*
pouvaient bien représenter comme configuration. Si les types KProperty0
et KProperty1
représentent une configuration assez classique, il n’en est rien pour le type KProperty2
.
La question qui nous vient alors naturellement à l’esprit est la suivante : « mais à quoi cela peut-il bien servir ? ». On peut voir le type KProperty2
comme la possibilité de représenter des propriétés qui ne sont valables que lorsque deux contextes sont réunis. On peut, par exemple, représenter la position d’une forme dans un conteneur avec le code ci-dessous (lien gist).
[java gutter= »true » language= »wraplines »]package com.github.sergiords.kproperties
class Form(val x: Int, val y: Int)
class Container(val x: Int, val y: Int) {
val Form.xInContainer: Int
get() = this.x – this@Container.x
val Form.yInContainer: Int
get() = this.y – this@Container.y
}[/java]
Dans l’exemple ci-dessus, une forme peut ainsi avoir une position absolue (Form.
x, Form.y
) et une position relative définie dans un conteneur (Form.xInContainer
, Form.yInContainer
). Cette dernière position bien que rattachée au type Form
, n’est définie que dans le contexte du type Container
. En dehors de ce contexte elle n’a pas de sens et n’est d’ailleurs pas accessible.
Commentaire
1 réponses pour " Les KProperty2 ou la réflexion signée Kotlin "
Published by Ampire , Il y a 6 ans
c’était très clair merci