127 votes

Covariance, invariance et contravariance expliquées en langage clair ?

Aujourd'hui, j'ai lu quelques articles sur la covariance, la contravariance (et l'invariance) en Java. J'ai lu l'article de Wikipedia en anglais et en allemand, ainsi que d'autres billets de blog et articles d'IBM.

Mais je suis toujours un peu confus sur ce que c'est exactement ? Certains disent qu'il s'agit de la relation entre les types et les sous-types, d'autres qu'il s'agit de la conversion des types et d'autres encore qu'ils sont utilisés pour décider si une méthode est surchargée ou non.

Je suis donc à la recherche d'une explication facile en anglais simple, qui montre à un débutant ce qu'est la covariance et la contravariance (et l'invariance). Un point de plus pour un exemple facile.

310voto

meriton Points 30447

Certains disent qu'il s'agit de la relation entre les types et les sous-types, d'autres disent qu'il s'agit de la conversion de type et d'autres encore disent qu'il est utilisé pour décider si une méthode est surchargée ou non.

Toutes ces réponses.

Au fond, ces termes décrivent comment la relation de sous-type est affectée par les transformations de type. Autrement dit, si A et B sont des types, f est une transformation de type, et ≤ la relation de sous-type (c'est-à-dire. A ≤ B signifie que A est un sous-type de B ), nous avons

  • f est covariant si A ≤ B implique que f(A) ≤ f(B)
  • f est contravariante si A ≤ B implique que f(B) ≤ f(A)
  • f est invariant si aucune des deux conditions ci-dessus n'est remplie.

Prenons un exemple. Soit f(A) = List<A>List est déclaré par

class List<T> { ... } 

Est f covariant, contravariant, ou invariant ? Covariant signifie qu'un List<String> est un sous-type de List<Object> , contravariant que a List<Object> est un sous-type de List<String> et invariant qu'aucun des deux n'est un sous-type de l'autre, à savoir List<String> et List<Object> sont des types inconvertibles. En Java, ce dernier point est vrai, nous disons (de manière quelque peu informelle) que génériques sont invariants.

Un autre exemple. Soit f(A) = A[] . Est f covariant, contravariant, ou invariant ? En d'autres termes, String[] est-il un sous-type d'Object[], Object[] un sous-type de String[], ou aucun des deux n'est-il un sous-type de l'autre ? (Réponse : en Java, les tableaux sont covariants).

C'était encore assez abstrait. Pour rendre les choses plus concrètes, voyons quelles opérations en Java sont définies en termes de relation de sous-type. L'exemple le plus simple est l'affectation. L'instruction

x = y;

ne compilera que si typeof(y) ≤ typeof(x) . C'est-à-dire que nous venons d'apprendre que les déclarations

ArrayList<String> strings = new ArrayList<Object>();
ArrayList<Object> objects = new ArrayList<String>();

ne compilera pas en Java, mais

Object[] objects = new String[1];

volonté.

Un autre exemple où la relation de sous-type est importante est l'expression d'invocation de méthode :

result = method(a);

De manière informelle, cette déclaration est évaluée en assignant la valeur de a au premier paramètre de la méthode, puis à l'exécution du corps de la méthode, et enfin à l'affectation de la valeur de retour de la méthode à la variable result . Comme pour l'affectation simple dans le dernier exemple, le "côté droit" doit être un sous-type du "côté gauche", c'est-à-dire que cette déclaration ne peut être valide que si typeof(a) ≤ typeof(parameter(method)) et returntype(method) ≤ typeof(result) . C'est-à-dire que si la méthode est déclarée par :

Number[] method(ArrayList<Number> list) { ... }

aucune des expressions suivantes ne sera compilée :

Integer[] result = method(new ArrayList<Integer>());
Number[] result = method(new ArrayList<Integer>());
Object[] result = method(new ArrayList<Object>());

mais

Number[] result = method(new ArrayList<Number>());
Object[] result = method(new ArrayList<Number>());

volonté.

Un autre exemple où le sous-typage est important est la surcharge. Pensez-y :

Super sup = new Sub();
Number n = sup.method(1);

class Super {
    Number method(Number n) { ... }
}

class Sub extends Super {
    @Override 
    Number method(Number n);
}

De manière informelle, le runtime réécrira ceci en :

class Super {
    Number method(Number n) {
        if (this instanceof Sub) {
            return ((Sub) this).method(n);  // *
        } else {
            ... 
        }
    }
}

Pour que la ligne marquée compile, le paramètre de méthode de la méthode surchargée doit être un supertype du paramètre de méthode de la méthode surchargée, et le type de retour un sous-type de celui de la méthode surchargée. Formellement parlant, f(A) = parametertype(method asdeclaredin(A)) doit au moins être contravariant, et si f(A) = returntype(method asdeclaredin(A)) doit au moins être covariante.

Notez le "au moins" ci-dessus. Il s'agit d'exigences minimales que tout langage de programmation orienté objet à sécurité statique raisonnable appliquera, mais un langage de programmation peut choisir d'être plus strict. Dans le cas de Java 1.4, les types de paramètres et les types de retour des méthodes doivent être identiques (sauf en ce qui concerne l'effacement des types) lorsque l'on surcharge des méthodes, à savoir parametertype(method asdeclaredin(A)) = parametertype(method asdeclaredin(B)) lorsque l'on passe outre. Depuis Java 1.5, les types de retour covariants sont autorisés en cas de surcharge, c'est-à-dire que l'exemple suivant compilera en Java 1.5, mais pas en Java 1.4 :

class Collection {
    Iterator iterator() { ... }
}

class List extends Collection {
    @Override 
    ListIterator iterator() { ... }
}

J'espère avoir tout couvert - ou plutôt, avoir gratté la surface. J'espère néanmoins que cela aidera à comprendre le concept abstrait, mais important, de la variance de type.

16voto

VadzimV Points 591

La variance concerne les relations entre les classes ayant des paramètres génériques différents. Leurs relations sont la raison pour laquelle nous pouvons les caster.

La variance de Co et Contra sont des choses assez logiques. Le système de type de langage nous oblige à soutenir la logique de la vie réelle. C'est facile à comprendre par l'exemple.

Covariance

Par exemple, vous voulez acheter une fleur et vous avez deux magasins de fleurs dans votre ville : le magasin de roses et le magasin de marguerites.

Si vous demandez à quelqu'un "où se trouve le magasin de fleurs ?" et qu'il vous répond "où se trouve le magasin de roses", est-ce que cela est acceptable ? Oui, car la rose est une fleur, si vous voulez acheter une fleur, vous pouvez acheter une rose. Il en va de même si quelqu'un vous répond en vous donnant l'adresse du magasin de marguerites. Voici un exemple de covariance : vous êtes autorisé à lancer A<C> à A<B>C est une sous-classe de B si A produit des valeurs génériques (retourne comme résultat de la fonction). La covariance concerne les producteurs.

Types :

class Flower {  }
class Rose extends Flower { }
class Daisy extends Flower { }

interface FlowerShop<T extends Flower> {
    T getFlower();
}

class RoseShop implements FlowerShop<Rose> {
    @Override
    public Rose getFlower() {
        return new Rose();
    }
}

class DaisyShop implements FlowerShop<Daisy> {
    @Override
    public Daisy getFlower() {
        return new Daisy();
    }
}

La question est "où est le magasin de fleurs ?", la réponse est "le magasin de roses est là" :

static FlowerShop<? extends Flower> tellMeShopAddress() {
    return new RoseShop();
}

Contravariance

Par exemple, vous voulez offrir une fleur à votre petite amie. Si votre petite amie aime n'importe quelle fleur, pouvez-vous la considérer comme une personne qui aime les roses, ou comme une personne qui aime les marguerites ? Oui, parce que si elle aime n'importe quelle fleur, elle aime aussi bien les roses que les marguerites. Ceci est un exemple de contravariance : vous êtes autorisé à lancer A<B> à A<C>C est une sous-classe de B si A consomme une valeur générique. La contravariance concerne les consommateurs.

Types :

interface PrettyGirl<TFavouriteFlower extends Flower> {
    void takeGift(TFavouriteFlower flower);
}

class AnyFlowerLover implements PrettyGirl<Flower> {
    @Override
    public void takeGift(Flower flower) {
        System.out.println("I like all flowers!");
    }

}

Tu considères ta petite amie qui aime toutes les fleurs comme quelqu'un qui aime les roses, et tu lui offres une rose :

PrettyGirl<? super Rose> girlfriend = new AnyFlowerLover();
girlfriend.takeGift(new Rose());

Pour en savoir plus, consultez le site Source : .

12voto

Joop Eggen Points 30166

Prendre le système de type java, et ensuite les classes :

Tout objet d'un certain type T peut être substitué par un objet de sous-type de T.

VARIANCE DE TYPE - LES MÉTHODES DE CLASSE ONT LES CONSÉQUENCES SUIVANTES

class A {
    public S f(U u) { ... }
}

class B extends A {
    @Override
    public T f(V v) { ... }
}

B b = new B();
t = b.f(v);
A a = ...; // Might have type B
s = a.f(u); // and then do V v = u;

On peut voir, que :

  • Le T doit être de sous-type S ( covariant, car B est un sous-type de A ).
  • Le V doit être un supertype de U ( contravariant comme sens contraire de l'héritage).

Maintenant, co- et contra- se rapportent au fait que B est un sous-type de A. Les typages plus forts suivants peuvent être introduits avec des connaissances plus spécifiques. Dans le sous-type.

La covariance (disponible en Java) est utile, pour dire que l'on renvoie un résultat plus spécifique dans le sous-type ; on le voit notamment lorsque A=T et B=S. La contravariance indique que vous êtes prêt à traiter un argument plus général.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X