Comprendre reified
types
Génériques
En utilisant les génériques en Kotlin, nous pouvons effectuer des opérations sur une valeur de n'importe quel type T
:
fun <T> doSomething(value: T) {
println("Doing something with value: $value") // OK
}
Ici, nous appelons implicitement le toString()
fonction de la value
et ça marche.
Mais nous ne pouvons pas effectuer d'opérations sur le type T
directement :
fun <T> doSomething(value: T) {
println("Doing something with type: ${T::class.simpleName}") // Error
}
Essayons de comprendre la raison de cette erreur.
Effacement de type
Dans le code ci-dessus, le compilateur donne une erreur : Cannot use 'T' as reified type parameter. Use a class instead.
Cela se produit parce qu'au moment de la compilation, le compilateur supprime l'argument de type de l'appel de fonction.
Par exemple, si vous appelez la fonction comme :
doSomething<String>("Some String")
Le compilateur supprime la partie argument de type <String>
et tout ce qui reste au moment de l'exécution est :
doSomething("Some String")
C'est ce qu'on appelle effacement de type . Ainsi, au moment de l'exécution (à l'intérieur de la définition de la fonction), nous ne pouvons pas savoir exactement quel est le type de la fonction T
représente.
Solution Java
La solution à ce problème d'effacement de type en Java consistait à passer un argument supplémentaire spécifiant le type avec l'attribut Class
(en Java) ou KClass
(en Kotlin) :
fun <T: Any> doSomething(value: T, type: KClass<T>) {
println("Doing something with type: ${type.simpleName}") // OK
}
De cette façon, notre code n'est pas affecté par l'effacement des types. Mais cette solution est verbeuse et pas très élégante puisque nous devons la déclarer ainsi que l'appeler avec un argument supplémentaire. De plus, le fait de spécifier le type lié Any
est obligatoire.
Réification du type
La meilleure solution au problème ci-dessus est la réification des types en Kotlin. Le site reified
avant le paramètre de type permet de conserver les informations de type lors de l'exécution :
inline fun <reified T> doSomething(value: T) {
println("Doing something with type: ${T::class.simpleName}") // OK
}
Dans le code ci-dessus, grâce au reified
nous n'obtenons plus l'erreur lors de l'exécution d'une opération sur le type T
. Voyons comment inline
rendent cette magie possible.
inline
fonctions
Lorsque nous marquons une fonction comme inline
le compilateur copie le corps réel de ce code. inline
partout où cette fonction est appelée. Puisque nous avons marqué notre doSomething()
fonction en tant que inline
le code suivant :
fun main() {
doSomething<String>("Some String")
}
est compilé :
fun main() {
println("Doing something with type: ${String::class.simpleName}")
}
Ainsi, les deux extraits de code présentés ci-dessus sont équivalents.
En copiant le corps d'un inline
le compilateur remplace également le paramètre de type T
avec l'argument de type réel qui est spécifié ou déduit dans l'appel de fonction. Par exemple, remarquez comment le paramètre de type T
est remplacé par l'argument de type réel String
.
Contrôle de type et moulage de type de reified
types
L'objectif principal d'un reified
paramètre de type est de connaître le type exact que le paramètre de type T
représente au moment de l'exécution.
Disons que nous avons une liste de différents types de fruits :
val fruits = listOf(Apple(), Orange(), Banana(), Orange())
Et nous voulons filtrer tous les Orange
dans une liste séparée comme suit :
val oranges = listOf(Orange(), Orange())
Sans reified
Pour filtrer les types de fruits, nous pouvons écrire une fonction d'extension sur List<Any>
comme les suivantes :
fun <T> List<Any>.filterFruit(): List<T> {
return this.filter { it is T }.map { it as T } // Error and Warning
}
Dans ce code, nous filtrons d'abord les types et ne prenons l'élément que si son type correspond à l'argument type donné. Ensuite, nous transférons chaque élément vers l'argument de type donné et return
le site List
. Mais il y a deux problèmes.
Vérification du type
Pendant la vérification du type it is T
nous sommes confrontés à une autre erreur du compilateur : Cannot check for instance of erased type: T
. Il s'agit d'un autre type d'erreur que vous pouvez rencontrer en raison de l'effacement des caractères.
Coulée de type
Alors que le moulage de type it as T
on nous donne aussi un avertissement : Unchecked cast: Any to T
. Le compilateur ne peut pas confirmer le type en raison de l'effacement du type.
reified
Les types à la rescousse
Nous pouvons facilement surmonter ces deux problèmes en marquant la fonction comme étant inline
et en rendant le paramètre de type reified
comme expliqué précédemment :
inline fun <reified T> List<Any>.filterFruit(): List<T> {
return this.filter { it is T }.map { it as T }
}
Et l'appeler comme suit :
val oranges = fruits.filterFruit<Orange>()
J'ai montré cette fonction pour faciliter la démonstration. Pour filtrer les types dans les collections, il existe déjà une fonction de la bibliothèque standard filterIsInstance()
. Cette fonction a utilisé le inline
y reified
de la même manière. Vous pouvez simplement l'appeler comme suit :
val oranges = fruits.filterIsInstance<Orange>()
Passing reified
en tant qu'argument
En reified
permet à une fonction de transmettre le paramètre de type en tant qu'argument de type à une autre fonction qui possède le même type de paramètre. reified
modificateur :
inline fun <reified T> doSomething() {
// Passing T as an argument to another function
doSomethingElse<T>()
}
inline fun <reified T> doSomethingElse() { }
Obtenir le type générique de la reified
type
Parfois, un argument de type peut être un type générique. Par exemple, List<String>
dans l'appel de fonction doSomething<List<String>>()
. Il est possible de connaître ce type entier, grâce à la réification :
inline fun <reified T> getGenericType() {
val type: KType = typeOf<T>()
println(type)
}
Ici, le typeOf()
est une fonction de la bibliothèque standard. Le site println()
La fonction ci-dessus imprimera kotlin.collections.List<kotlin.String>
si vous appelez la fonction comme getGenericType<List<String>>()
. Le site KType
comprend KClass
les informations sur les arguments de type et les informations sur la nullité. Une fois que vous connaissez le KType
vous pouvez y réfléchir.
Interopérabilité de Java
En inline
Les fonctions déclarées sans reified
peuvent être appelés depuis Java comme des fonctions Java ordinaires. Mais ceux qui sont déclarés avec l'attribut reified
ne sont pas appelables depuis Java.
Même si vous l'appelez en utilisant la réflexion comme suit :
Method method = YourFilenameKt.class.getDeclaredMethod("doSomething", Object.class);
method.invoke("hello", Object.class);
Vous obtenez le UnsupportedOperationException: This function has a reified type parameter and thus can only be inlined at compilation time, not called directly.
Conclusion
Dans de nombreux cas, le reified
nous aident à nous débarrasser des erreurs et avertissements suivants :
Error: Cannot use 'T' as reified type parameter. Use a class instead.
Error: Cannot check for instance of erased type: T
Warning: Unchecked cast: SomeType to T
C'est ça ! J'espère que cela vous aidera à comprendre l'essence de reified
types.
11 votes
Les paramètres de type générique sont effacés au moment de l'exécution, lisez ce qui concerne l'effacement des types si ce n'est pas déjà fait. Les paramètres de type réifié sur les fonctions inline n'intègrent pas seulement le corps de la méthode, mais aussi les paramètres de type générique. 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). Je le mets en commentaire parce que je n'ai pas le temps d'étoffer une réponse complète pour le moment
1 votes
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.