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 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
c'est un sous-type de B
), nous avons
f
est covariant si cela A ≤ B
implique quef(A) ≤ f(B)
f
est contravariant si cela A ≤ B
implique quef(B) ≤ f(A)
f
est invariant si aucune des réponses ci-dessus ne tient
Prenons un exemple. Soit f(A) = List<A>
où List
est déclaré par
class List<T> { ... }
Est-ce f
covariant, 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 f
covariant, 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 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 primordial. Considérer:
Super sup = new Sub();
Number n = sup.method(1);
où
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.