lundi 30 mars 2015

Covariance, Contravariance et Invariant

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 :


La liste reste accessible en lecture et écriture :




Aucun commentaire:

Enregistrer un commentaire