Covariance, Invariance et Contravariance expliquées en anglais simple?

113

Aujourd'hui, j'ai lu quelques articles sur la Covariance, la Contravariance (et l'Invariance) en Java. J'ai lu l'article de Wikipédia en anglais et en allemand, ainsi que d'autres articles 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, certains disent qu'il s'agit de conversion de type et certains disent qu'elle est utilisée pour décider si une méthode est remplacée ou surchargée.

Je cherche donc une explication simple en anglais simple, qui montre à un débutant ce que sont la covariance et la contravariance (et l'invariance). Point plus pour un exemple simple.

tzrm
la source
Veuillez vous référer à cet article, il peut être utile pour vous: stackoverflow.com/q/2501023/218717
Francisco Alvarado
3
Peut-être mieux une question de type d'échange de pile d'un programmeur. Si vous postez là-bas, pensez à indiquer ce que vous comprenez et ce qui vous déroute spécifiquement, car en ce moment, vous demandez à quelqu'un de réécrire tout un didacticiel pour vous.
Aéroglisseur plein d'anguilles

Réponses:

288

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

Tout ce qui précède.

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

  • fest covariant si cela A ≤ Bimplique quef(A) ≤ f(B)
  • fest contravariant si cela A ≤ Bimplique quef(B) ≤ f(A)
  • f est invariant si aucune des réponses ci-dessus ne tient

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

class List<T> { ... } 

Est-ce fcovariant, contravariant ou invariant? Covariant signifierait que a List<String>est un sous-type de List<Object>, contravariant que a List<Object>est un sous-type de List<String>et invariant que ni l'un ni l'autre n'est un sous-type de l'autre, c'est List<String>-à- dire et List<Object>sont des types inconvertibles. En Java, ce dernier est vrai, nous disons (de manière informelle) que les génériques sont invariants.

Un autre exemple. Laissez f(A) = A[]. Est-ce fcovariant, contravariant ou invariant? Autrement dit, String [] est-il un sous-type de Object [], Object [] un sous-type de String [], ou n'est-il pas un sous-type de l'autre? (Réponse: en Java, les tableaux sont covariants)

C'était encore assez abstrait. Pour le rendre plus concret, regardons quelles opérations en Java sont définies en termes de relation de sous-type. L'exemple le plus simple est l'affectation. La déclaration

x = y;

ne compilera que si typeof(y) ≤ typeof(x). Autrement dit, 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 une expression d'invocation de méthode:

result = method(a);

De manière informelle, cette instruction est évaluée en attribuant la valeur de aau premier paramètre de la méthode, puis en exécutant le corps de la méthode, puis en affectant la valeur de retour des méthodes à result. Comme 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). Autrement dit, 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 primordial. Considérer:

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 pour:

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

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

Notez le "au moins" ci-dessus. Ce sont des exigences minimales que tout langage de programmation orienté objet sécurisé de type statique raisonnable imposera, 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 de méthode doivent être identiques (sauf pour l'effacement de type) lors de la substitution de méthodes, c'est- parametertype(method asdeclaredin(A)) = parametertype(method asdeclaredin(B))à- dire lors de la substitution. Depuis Java 1.5, les types de retour covariants sont autorisés lors de la substitution, c'est-à-dire que les éléments suivants seront compilés en Java 1.5, mais pas en Java 1.4:

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

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

J'espère que j'ai tout couvert - ou plutôt, rayé la surface. J'espère toujours que cela aidera à comprendre le concept abstrait mais important de la variance de type.

meriton
la source
1
De plus, puisque les types d'arguments contravariants Java 1.5 sont autorisés lors de la substitution. Je pense que tu as raté ça.
Brian Gordon
13
Sont-ils? Je viens de l'essayer dans eclipse, et le compilateur a pensé que je voulais surcharger plutôt que remplacer, et a rejeté le code lorsque j'ai placé une annotation @Override sur la méthode de sous-classe. Avez-vous des preuves de votre affirmation selon laquelle Java prend en charge les types d'arguments contravariants?
meriton
1
Ah, tu as raison. J'ai cru quelqu'un sans le vérifier moi-même.
Brian Gordon
1
J'ai lu beaucoup de documentation et regardé quelques discussions sur ce sujet, mais c'est de loin la meilleure explication. Thnx beaucoup.
minzchickenflavor
1
+1 pour être absolument leman et simple avec A ≤ B. Cette notation rend les choses beaucoup plus simples et significatives. Bonne lecture ...
Romeo Sierra
12

Prenant le système de type java, puis les classes:

Tout objet d'un certain type T peut être remplacé par un objet d'un 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 le sous-type S ( covariant, car B est le sous-type de A ).
  • Le V doit être un supertype de U ( contravariant , comme direction d'héritage contra).

Maintenant co- et contre- se rapportent au sous-type B 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; surtout vu quand A = T et B = S. Contravariance dit que vous êtes prêt à gérer un argument plus général.

Joop Eggen
la source
8

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

La variance Co et Contra sont des choses assez logiques. Le système de type de langage nous oblige à prendre en charge la logique 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: un magasin de roses et un magasin de marguerites.

Si vous demandez à quelqu'un "où est le magasin de fleurs?" et quelqu'un vous dit où est le magasin de roses, est-ce que ça ira? oui, parce que 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 a répondu avec l'adresse de la marguerite. Voici un exemple de covariance : vous êtes autorisé à effectuer un cast A<C>vers A<B>, où Cest une sous-classe de B, si Aproduit des valeurs génériques (retourne comme résultat de la fonction). La covariance concerne les producteurs.

Les 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 "magasin de roses là-bas":

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

Contravariance

Par exemple, vous souhaitez offrir une fleur à votre petite amie. Si votre petite amie aime une fleur, pouvez-vous la considérer comme une personne qui aime les roses ou comme une personne qui aime les marguerites? oui, car si elle aime une fleur, elle aimerait à la fois la rose et la marguerite. Voici un exemple de contravariance : vous êtes autorisé à effectuer un cast A<B>vers A<C>, où Cest la sous-classe de B, si Aconsomme une valeur générique. La contravariance concerne les consommateurs.

Les 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!");
    }

}

Vous considérez votre petite amie qui aime toutes les fleurs comme quelqu'un qui aime les roses et vous lui donnez une rose:

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

Vous pouvez en trouver plus à la Source .

VadzimV
la source
@Peter, merci, c'est un bon point. L'invariance c'est quand il n'y a pas de relations entre les classes avec des paramètres génériques différents, c'est-à-dire que vous ne pouvez pas convertir A <B> en A <C> quelle que soit la relation entre B et C.
VadzimV