Afin de rendre les classes et les méthodes plus génériques possibles, Java nous propose 3 niveaux de "généricité" : Covariance, Contravariance et Invariance. Je vous propose dans ce petit article, une brève présentation de ces 3 concepts en m'inspirant des exemples simples.
Pour commencer, voici un petit diagramme de classe ainsi que le code des classes associées permettant d’illustrer mes propos :
Nous disposons également de trois méthodes avec une liste générique en paramètre de chaque méthode :
/** * * @param ovals */ static void toDoInvariant(List<Oval> ovals) { //..... } /** * * @param ovals */ static void toDoCovariance(List<? extends Oval> ovals) { //..... } /** * * @param ovals */ static void toDoContravariance(List<? super Oval> ovals) { //..... }
Covariance
Une classe C<T> est en
covariance en T si toute classe A, B : si A est une sous-classe de B alors
C<A> est une sous classe de C<B>. C’est une façon de spécifier que
toutes les classes filles/les sous-classes sont acceptables.
Un exemple de covariance en Scala (T = [+A], la classe Vector est nativement covariante) ou en Java T = ? extends A avec A une classe ou une interface. À la place de T, il est possible d’utiliser tout type (B et C) qui
extends/implements A (B/C etends A ou implements A).
La covariance permet de définir un type qui peut recevoir n’importe quel
type plus spécifique en limitant son utilisation à partir d’un type parent (A). Tout A ou qui l’hérite (direct ou indirect) est accepté.Le type A peut être vu comme la borne supérieure dans l’arborescence héritage de classes :
La méthode toDoCovariance prenant en paramètre une liste du type covariant ( ? extends Oval ) n'accepte que les type héritant de la classe Oval (la classe Oval comprise !) :
L’utilisation d’une covariance sur une liste la
rend immutable. La liste est accessible en lecteur seule (Read-Only). L'appel de la méthode add sur la liste ovals engendre une erreur de compilation :
Contravariance
Une classe
C<T> est en contravariance en T si toute classe A, B : si A est une super-classe
de B alors C<A> est une super classe de C<B>. C’est une autre façon
de spécifier que toutes les classes parents/les super-classes sont acceptables.
En Scala, la contravariance est notée par [-A] avec A une classe ou une interface. Un exemple en Java T = ? super A. Le type T peut être remplacé par tout type (B, C) parent de
A (A implemnts B ou A extends C).
A l’inverse de la covariance, la contravariance permet de définir un
type qui peut recevoir n’importe quel type moins spécifique. Le type A ( ? super A) détermine
une sorte de borne inférieure : tout type parent direct ou indirect de A
est accepté :
La méthode toDoContravariance prenant en paramètre une liste du type contravariant ( ? super Oval ) n'accepte que les type parent de la classe Oval (la classe Oval comprise) :
Une liste avec un type contravariant est
accessible en écriture seule (Write-Only). Cependant, l’accès aux éléments reste
possible mais seulement sous forme d’Object. Dans l'appel de la méthode toDoContravariance , nous remarquons bien que Object objectOval = ovals.get(0); ne génère pas de problème de compilation :
Invariance
Un type est invariant signifie que vous pouvez utiliser uniquement le type spécifié à
l'origine. Un type invariant n’est pas modifiable ni pour le passer en paramètre de
méthode ou pour l’affecter à une autre instance. Il est par exemple impossible d'affecter une liste de Forme à une liste de Rectangle :
La méthode toDInvariant n'accepte qu'une liste d'Oval :
Aucun commentaire:
Enregistrer un commentaire