web-dev-qa-db-fra.com

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

Aujourd'hui, j'ai lu quelques articles sur la covariance, la contrevariance (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 encore un peu perplexe sur ce que cela signifie exactement? Certains disent qu'il s'agit de la relation entre les types et les sous-types, certains disent qu'il s'agit de la conversion de type et certains disent qu'il est utilisé pour décider si une méthode est remplacée ou surchargée.

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

106
anon

Certains disent qu'il s'agit d'une relation entre les types et les sous-types, d'autres disent qu'il s'agit d'une 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 A et B sont des types, f est une transformation de type et ≤ la relation de sous-type (c.-à-d. 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 contravariant si A ≤ B implique que f(B) ≤ f(A)
  • f est invariant si aucune des conditions ci-dessus ne tient

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

class List<T> { ... } 

f est-il covariant, contravariant ou invariant? Covariant signifierait qu'un List<String> Est un sous-type de List<Object>, Contravariant qu'un List<Object> Est un sous-type de List<String> Et invariant que ni l'un ni l'autre n'est un sous-type du d'autres, c'est-à-dire List<String> et List<Object> sont des types non convertibles. En Java, ce dernier est vrai, nous disons (quelque peu informellement) que génériques sont invariants.

Un autre exemple. Soit f(A) = A[]. f est-il covariant, contravariant ou invariant? Autrement dit, String [] est-il un sous-type de Object [], Object [] un sous-type de String [], ou n'est-ce 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 dans Java sont définies en termes de relation de sous-type. L'exemple le plus simple est l'affectation. L'instruction

x = y;

compilera uniquement 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 affectant la valeur de a au 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 important est prioritaire. 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 en:

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 prioritaire doit être un supertype du paramètre de méthode de la méthode substituée et le type de retour un sous-type de celui de la méthode substitué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 covariant.

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-à-dire parametertype(method asdeclaredin(A)) = parametertype(method asdeclaredin(B)) lors de la substitution. Puisque 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 avoir tout couvert - ou plutôt rayé la surface. J'espère néanmoins que cela aidera à comprendre le concept abstrait, mais important, de la variance de type.

257
meriton

Prendre le système de type Java, puis les classes:

Tout objet d'un certain type T peut être remplacé par un objet de sous-type 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 sens d'hérédité).

Maintenant, co et contre-se rapportent à B étant le 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; surtout vu quand A = T et B = S. La contravariance indique que vous êtes prêt à gérer un argument plus général.

11
Joop Eggen