Comment fonctionne le mot-clé réifié dans Kotlin?

144

J'essaie de comprendre le but du reifiedmot - clé, apparemment cela nous permet de faire une réflexion sur les génériques .

Cependant, quand je le laisse de côté, cela fonctionne tout aussi bien. Quelqu'un veut-il expliquer quand cela fait une réelle différence ?

hl3mukkel
la source
Etes-vous sûr de savoir ce qu'est la réflexion? Aussi, avez-vous entendu parler de l'effacement de type?
Mibac
3
Les paramètres de type générique sont effacés au moment de l'exécution, lisez sur l'effacement de type si vous ne l'avez pas déjà fait. Les paramètres de type réifiés sur les fonctions en ligne non seulement inline le corps de la méthode, mais également le paramètre de type générique vous permettant de faire des choses comme T :: class.java (ce que vous ne pouvez pas faire avec les types génériques normaux). Mettre en commentaire parce que je n'ai pas le temps d'étoffer une réponse complète pour le moment ..
F. George
Il permet d'accéder au type générique concret d'une fonction sans s'appuyer sur la réflexion et sans avoir à passer le type en argument.
BladeCoder

Réponses:

368

TL; DR: Ce qui est reifiedbon pour

fun <T> myGenericFun(c: Class<T>) 

Dans le corps d'une fonction générique comme myGenericFun, vous ne pouvez pas accéder au type Tcar il est uniquement disponible au moment de la compilation mais effacé au moment de l'exécution. Par conséquent, si vous souhaitez utiliser le type générique en tant que classe normale dans le corps de la fonction, vous devez transmettre explicitement la classe en tant que paramètre, comme indiqué dans myGenericFun.

Si vous créez une inlinefonction avec un reified T , le type de Tpeut être consulté même au moment de l'exécution et vous n'avez donc pas besoin de passer le Class<T>. Vous pouvez travailler avec Tcomme si elle était une classe normale, par exemple , vous pouvez vérifier si une variable est une instance de T , que vous pouvez facilement faire alors: myVar is T.

Une telle inlinefonction avec reifiedtype Tressemble à ceci:

inline fun <reified T> myGenericFun()

Comment reifiedfonctionne

Vous ne pouvez utiliser reifiedqu'en combinaison avec une inlinefonction . Une telle fonction oblige le compilateur à copier le bytecode de la fonction à chaque endroit où la fonction est utilisée (la fonction est "en ligne"). Lorsque vous appelez une fonction en ligne avec un type réifié, le compilateur connaît le type réel utilisé comme argument de type et modifie le bytecode généré pour utiliser directement la classe correspondante. Par conséquent, les appels comme myVar is Tdeviennent myVar is String(si l'argument type l'était String) dans le bytecode et à l'exécution.


Exemple

Jetons un coup d'œil à un exemple qui montre à quel point cela reifiedpeut être utile . Nous voulons créer une fonction d'extension pour Stringcalled toKotlinObjectqui tente de convertir une chaîne JSON en un objet Kotlin simple avec un type spécifié par le type générique de la fonction T. Nous pouvons utiliser com.fasterxml.jackson.module.kotlinpour cela et la première approche est la suivante:

a) Première approche sans type réifié

fun <T> String.toKotlinObject(): T {
      val mapper = jacksonObjectMapper()
                                                        //does not compile!
      return mapper.readValue(this, T::class.java)
}

La readValueméthode prend un type qu'elle est censée analyser le JsonObjectto. Si nous essayons d'obtenir le Classparamètre de type T, le compilateur se plaint: "Impossible d'utiliser 'T' comme paramètre de type réifié. Utilisez une classe à la place."

b) Solution de contournement avec un Classparamètre explicite

fun <T: Any> String.toKotlinObject(c: KClass<T>): T {
    val mapper = jacksonObjectMapper()
    return mapper.readValue(this, c.java)
}

Pour contourner le problème, vous pouvez faire de Classof Tun paramètre de méthode, qui sera ensuite utilisé comme argument de readValue. Cela fonctionne et est un modèle courant dans le code Java générique. Il peut être appelé comme suit:

data class MyJsonType(val name: String)

val json = """{"name":"example"}"""
json.toKotlinObject(MyJsonType::class)

c) La voie Kotlin: reified

L'utilisation d'une inlinefonction avec reifiedparamètre de type Tpermet d'implémenter la fonction différemment:

inline fun <reified T: Any> String.toKotlinObject(): T {
    val mapper = jacksonObjectMapper()
    return mapper.readValue(this, T::class.java)
}

Il n'est pas nécessaire de prendre le Classde en Tplus, Tpeut être utilisé comme s'il s'agissait d'une classe ordinaire. Pour le client, le code ressemble à ceci:

json.toKotlinObject<MyJsonType>()

Remarque importante: utilisation de Java

Une fonction inline avec reifiedtype ne peut pas être appelée à partir du code Java .

s1m0nw1
la source
6
Merci pour votre réponse complète! Cela a du sens. Juste une chose que je me demande, pourquoi la réification est-elle nécessaire si la fonction est de toute façon intégrée? Il laisserait l'effacement de type et la fonction en ligne de toute façon? Cela me semble un peu un gaspillage, si vous intégrez la fonction, vous pourriez également intégrer le type utilisé ou est-ce que je vois quelque chose qui ne va pas ici?
hl3mukkel
6
Merci pour vos commentaires, en fait, j'ai oublié de mentionner quelque chose qui pourrait vous donner la réponse: une fonction inline normale peut être appelée à partir de Java, mais une fonction avec un paramètre de type réifié ne le peut pas! Je pense que c'est une raison pour laquelle tous les paramètres de type d'une fonction en ligne ne sont pas automatiquement réifiés.
s1m0nw1
Et si la fonction était un mélange de paramètres réifiés et non réifiés? Cela le rend de toute façon non éligible pour être appelé depuis Java, pourquoi ne pas réifier automatiquement tous les paramètres de type? Pourquoi kotlin doit-il avoir réifié spécifié explicitement pour tous les paramètres de type?
Vairavan le
1
et si les appelants supérieurs de la pile n'ont pas besoin de json.toKotlinObject <MyJsonType> (), mais de json.toKotlinObject <T> () pour différents objets?
sept
1

FACILE

* réifié est de donner la permission d'utiliser au moment de la compilation (pour accéder à T à l'intérieur de la fonction)

par exemple:

 inline fun <reified T:Any>  String.convertToObject(): T{

    val gson = Gson()

    return gson.fromJson(this,T::class.java)

}

en utilisant comme:

val jsonStringResponse = "{"name":"bruno" , "age":"14" , "world":"mars"}"
val userObject = jsonStringResponse.convertToObject<User>()
  println(user.name)
poiu
la source