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 A
e B
sono tipi, f
è una trasformazione di tipo e ≤ la relazione del sottotipo (cioè A ≤ B
significa che A
è un sottotipo di B
), abbiamo
f
è covariante se lo A ≤ B
implicaf(A) ≤ f(B)
f
è controvariante se lo A ≤ B
implicaf(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> { ... }
È f
covariante, 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[]
. È f
covariante, 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 a
al 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.