Alcuni dicono che si tratta di relazione tra tipi e sottotipi, altri dicono che si tratta di conversione del tipo e altri dicono che è usato per decidere se un metodo viene sovrascritto o sovraccaricato.
Tutti i precedenti.
In sostanza, questi termini descrivono in che modo la relazione del sottotipo è influenzata dalle trasformazioni di tipo. Cioè, se Ae Bsono tipi, fè una trasformazione di tipo e ≤ la relazione del sottotipo (cioè A ≤ Bsignifica che Aè un sottotipo di B), abbiamo
fè covariante se lo A ≤ Bimplicaf(A) ≤ f(B)
fè controvariante se lo A ≤ Bimplicaf(B) ≤ f(A)
f è invariante se nessuna delle precedenti è valida
Consideriamo un esempio. Lasciate f(A) = List<A>dove Listè dichiarato da
class List<T> { ... }
È fcovariante, controvariante o invariante? Covariante significherebbe che una List<String>è un sottotipo di List<Object>, controvariante che List<Object>è un sottotipo di List<String>ed invariante che né è un sottotipo dell'altro, cioè List<String>e List<Object>sono tipi inconvertibili. In Java, quest'ultimo è vero, diciamo (un po 'informalmente) che i generici sono invarianti.
Un altro esempio. Let f(A) = A[]. È fcovariante, controvariante o invariante? Cioè, String [] è un sottotipo di Object [], Object [] un sottotipo di String [], o non è né un sottotipo dell'altro? (Risposta: in Java, gli array sono covarianti)
Questo era ancora piuttosto astratto. Per renderlo più concreto, diamo un'occhiata a quali operazioni in Java sono definite in termini di relazione del sottotipo. L'esempio più semplice è l'assegnazione. La dichiarazione
x = y;
compilerà solo se typeof(y) ≤ typeof(x). Cioè, abbiamo appena appreso che le dichiarazioni
ArrayList<String> strings = new ArrayList<Object>();
ArrayList<Object> objects = new ArrayList<String>();
non verrà compilato in Java, ma
Object[] objects = new String[1];
volere.
Un altro esempio in cui la relazione del sottotipo è importante è un'espressione di invocazione del metodo:
result = method(a);
In modo informale, questa istruzione viene valutata assegnando il valore di aal primo parametro del metodo, quindi eseguendo il corpo del metodo e quindi assegnando il valore restituito ai metodi result. Come l'assegnazione semplice nell'ultimo esempio, il "lato destro" deve essere un sottotipo del "lato sinistro", ovvero questa affermazione può essere valida solo se typeof(a) ≤ typeof(parameter(method))e returntype(method) ≤ typeof(result). Cioè, se il metodo è dichiarato da:
Number[] method(ArrayList<Number> list) { ... }
nessuna delle seguenti espressioni verrà compilata:
Integer[] result = method(new ArrayList<Integer>());
Number[] result = method(new ArrayList<Integer>());
Object[] result = method(new ArrayList<Object>());
ma
Number[] result = method(new ArrayList<Number>());
Object[] result = method(new ArrayList<Number>());
volere.
Un altro esempio in cui la sottotipizzazione è importante. Tener conto di:
Super sup = new Sub();
Number n = sup.method(1);
dove
class Super {
Number method(Number n) { ... }
}
class Sub extends Super {
@Override
Number method(Number n);
}
In modo informale, il runtime lo riscriverà in:
class Super {
Number method(Number n) {
if (this instanceof Sub) {
return ((Sub) this).method(n); // *
} else {
...
}
}
}
Per la compilazione della riga contrassegnata, il parametro del metodo del metodo sovrascritto deve essere un supertipo del parametro del metodo del metodo sovrascritto e il tipo restituito un sottotipo di quello del metodo sostituito. Formalmente parlando, f(A) = parametertype(method asdeclaredin(A))deve almeno essere controvariante e se f(A) = returntype(method asdeclaredin(A))deve essere almeno covariante.
Notare "almeno" sopra. Questi sono requisiti minimi che qualsiasi linguaggio di programmazione orientato agli oggetti ragionevole e staticamente sicuro imporrà, ma un linguaggio di programmazione può scegliere di essere più rigoroso. Nel caso di Java 1.4, i tipi di parametro ei tipi di restituzione del metodo devono essere identici (eccetto per la cancellazione del tipo) quando si sovrascrivono i metodi, cioè parametertype(method asdeclaredin(A)) = parametertype(method asdeclaredin(B))quando si sovrascrive. A partire da Java 1.5, i tipi restituiti covarianti sono consentiti durante l'override, ovvero quanto segue verrà compilato in Java 1.5, ma non in Java 1.4:
class Collection {
Iterator iterator() { ... }
}
class List extends Collection {
@Override
ListIterator iterator() { ... }
}
Spero di aver coperto tutto, o meglio, di aver graffiato la superficie. Tuttavia spero che possa aiutare a capire il concetto astratto ma importante della varianza di tipo.