réflexion avec Kotlin

Réflexion avec Kotlin

1. introduction

Reflection désigne la possibilité d'inspecter, de charger et d'interagir avec les classes, les champs et les méthodes au moment de l'exécution. Nous pouvons le faire même si nous ne savons pas ce qu’ils sont au moment de la compilation.

Cela a un grand nombre d'utilisations, en fonction de ce que nous développons. Par exemple, les frameworks comme Spring en font un usage intensif.

La prise en charge de cela est intégrée à la machine virtuelle Java et est donc implicitement disponible pour tous les langages basés sur la machine virtuelle Java. Cependant, certaines langues de la machine virtuelle Java disposent d'un support supplémentaire par rapport à ce qui est déjà disponible.

2. Réflexion Java

All the standard Java Reflection constructs are available and work perfectly well with our Kotlin code. Cela inclut la classejava.lang.Class ainsi que tout ce qui se trouve dans le packagejava.lang.reflect.

Si nous voulons utiliser les API Java Reflection standard pour quelque raison que ce soit, nous pouvons le faire exactement de la même manière qu'en Java. Par exemple, pour obtenir une liste de toutes les méthodes publiques dans une classe Kotlin, nous ferions:

MyClass::class.java.methods

Cela se décompose dans les constructions suivantes:

  • MyClass::class nous donne la représentation de la classe Kotlin pour la classeMyClass

  • .java nous donne l'équivalentjava.lang.Class

  • .methods est un appel à la méthode d'accesseurjava.lang.Class.getMethods()

This will work exactly the same whether called from Java or Kotlin, and whether called on a Java or a Kotlin class. Cela inclut les constructions spécifiques à Kotlin, telles que les classes de données.

data class ExampleDataClass(
  val name: String, var enabled: Boolean)

ExampleDataClass::class.java.methods.forEach(::println)

Kotlin convertit également les types renvoyés en représentations Kotlin.

Dans ce qui précède, nous obtenons unkotlin.Array<Method> sur lequel nous pouvons appelerforEach().

3. Améliorations de la réflexion de Kotlin

Whilst we can use the standard Java Reflection APIs, it isn’t aware of all extensions that Kotlin brings to the platform.

De plus, il peut parfois être un peu difficile à utiliser dans certaines situations. Kotlin apporte sa propre API de réflexion que nous pouvons utiliser pour résoudre ces problèmes.

Tous les points d’entrée de l’API Kotlin Reflection utilisent des références. Plus tôt, nous avons vu l'utilisation de::class pour donner une référence à la définition de classe. Nous pourrons également l'utiliser pour obtenir des références à des méthodes et des propriétés.

3.1. Références de classe Kotlin

The Kotlin Reflection API allows access to a Class reference. This can then be used to introspect the full details of the Kotlin class. Cela donne accès à la référence de la classe Java - l'objetjava.lang.Class - mais aussi à tous les détails spécifiques à Kotlin.

L'API Kotlin pour les détails de classe est centrée sur la classekotlin.reflect.KClass. This can be accessed by using the :: operator from any class name or instance - par exemple String::class.

Vous pouvez également y accéder en utilisant la méthode d'extensionjava.lang.Class.kotlin si une instance JavaClass est disponible pour nous:

val listClass: KClass = List::class

val name = "example"
val stringClass: KClass = name::class

val someClass: Class
val kotlinClass: KClass = someClass.kotlin

Once we have obtained a KClass object, there are some simple things that it can tell us about the class in question. Certains d'entre eux sont des concepts Java standard, d'autres des concepts spécifiques à Kotlin.

Par exemple, nous pouvons facilement savoir si une classe est abstraite ou finale, mais nous pouvons également savoir si la classe est une classe de données ou une classe d'accompagnement:

val stringClass = String::class
assertEquals("kotlin.String", stringClass.qualifiedName)
assertFalse(stringClass.isData)
assertFalse(stringClass.isCompanion)
assertFalse(stringClass.isAbstract)
assertTrue(stringClass.isFinal)
assertFalse(stringClass.isSealed)

Nous avons également des moyens de nous déplacer dans la hiérarchie des classes. En Java, nous pouvons déjà passer d'une classe à sa superclasse, à ses interfaces et à la classe externe dans laquelle elle est incluse - le cas échéant.

Kotlin ajoute à cela la possibilité d'obtenir l'objet compagnon pour une classe arbitraire et l'instanceObject pour une classe Object:

println(TestWithCompanion::class.companionObject)
println(TestWithCompanion::class.companionObjectInstance)
println(TestObject::class.objectInstance)

We can create new instances of a class from a Class Reference as well, à peu près de la même manière qu'en Java:

val listClass = ArrayList::class

val list = listClass.createInstance()
assertTrue(list is ArrayList)

Alternativement, nous pouvons accéder aux constructeurs et utiliser un explicite si nous en avons besoin. Celles-ci sont toutes des références de méthode, comme indiqué dans la section suivante.

De manière très similaire, nous pouvons accéder à toutes les méthodes, propriétés, extensions et autres membres de la classe:

val bigDecimalClass = BigDecimal::class

println(bigDecimalClass.constructors)
println(bigDecimalClass.functions)
println(bigDecimalClass.memberProperties)
println(bigDecimalClass.memberExtensionFunctions)

3.2. Références de la méthode Kotlin

En plus de pouvoir interagir avec les classes,we can also interact with Methods and Properties.

Cela inclut les propriétés de classe - définies avecval ouvar, les méthodes de classe standard et les fonctions de niveau supérieur. Comme auparavant, cela fonctionne aussi bien sur du code écrit en Java standard que sur du code écrit en Kotlin.

De la même manière qu'avec les classes,we can obtain a reference to a Method or Property using the:: operator.

Cela ressemble exactement à Java 8 pour obtenir une référence à une méthode, et on peut l’utiliser exactement de la même manière. Cependant, dans Kotlin, cette référence de méthode peut également être utilisée pour obtenir des informations de réflexion sur la cible.

Once we have obtained a method reference, we can call it as if was really the method in question. Ceci est connu comme étant une référence appelable:

val str = "Hello"
val lengthMethod = str::length

assertEquals(5, lengthMethod())

Nous pouvons également obtenir plus de détails sur la méthode elle-même, de la même manière que pour les classes. Cela inclut à la fois les détails Java standard et les détails spécifiques à Kotlin, par exemple si la méthode est unoperator ou s'il s'agit deinline:

val byteInputStream = String::byteInputStream
assertEquals("byteInputStream", byteInputStream.name)
assertFalse(byteInputStream.isSuspend)
assertFalse(byteInputStream.isExternal)
assertTrue(byteInputStream.isInline)
assertFalse(byteInputStream.isOperator)

In addition to this, we can get more information about the inputs and outputs of the method through this reference.

Cela inclut des détails sur le type de retour et les paramètres, y compris des détails spécifiques à Kotlin, tels que la nullabilité et l'opportunité.

val str = "Hello"
val method = str::byteInputStream

assertEquals(
  ByteArrayInputStream::class.starProjectedType,
  method.returnType)
assertFalse(method.returnType.isMarkedNullable)

assertEquals(1, method.parameters.size)
assertTrue(method.parameters[0].isOptional)
assertFalse(method.parameters[0].isVararg)
assertEquals(
  Charset::class.starProjectedType,
  method.parameters[0].type)

3.3. Références de propriété Kotlin

This works exactly the same for Properties as well, bien que évidemment, les détails qui peuvent être obtenus sont différents. Les propriétés à la place peuvent nous indiquer s'il s'agit de constantes, d'initialisation tardive ou de mutables:

lateinit var mutableProperty: String
val mProperty = this::mutableProperty
assertEquals("mutableProperty", mProperty.name)
assertTrue(mProperty.isLateinit)
assertFalse(mProperty.isConst)
assertTrue(mProperty is KMutableProperty<*>)

Note that the concept of Properties also works in any non-Kotlin code. These are identified by fields that follow the JavaBeans conventions regarding getter and setter methods.

Cela inclut les classes de la bibliothèque standard Java. Par exemple, la classeThrowable a une propriétéThrowable.message en vertu du fait qu'une méthodegetMessage() y est définie.

We can access the actual Property through Method references that are exposed - les méthodesgetter etsetter. Lesetter n'est disponible que si nous travaillons avec unKMutableProperty – i.e. la propriété a été déclarée commevar, alors quegetter est toujours disponible.

Celles-ci sont exposées de manière plus simple à utiliser via les méthodesget() etset(). Les valeursgetter etsetter sont des références de méthode réelles, ce qui nous permet de travailler avec elles exactement de la même manière que toute autre référence de méthode:

val prop = this::mutableProperty

assertEquals(
  String::class.starProjectedType,
  prop.getter.returnType)

prop.set("Hello")
assertEquals("Hello", prop.get())

prop.setter("World")
assertEquals("World", prop.getter())

4. Sommaire

Cet article donne un aperçu des tâches pouvant être réalisées avec la réflexion dans Kotlin, notamment en ce qui concerne son interaction avec les capacités de réflexion intégrées au langage Java standard.

Tous les exemples sont disponiblesover on GitHub.