C'è molta incomprensione nella domanda:
Innanzitutto, non esiste una "varianza di tipi parametrici". I tipi (come List[Int]
) sono, da soli, corretti, e quindi non varianti (co / contro-variante). Cosa può essere una variante (ntra), sono costruttori di tipi , ad es. List
, che non sono essi stessi tipi.
Pertanto, la linea
<A extends B,B> void store(ArrayList<B> list, A elem)
non può "indicare la varianza dei tipi", poiché non definisce alcun costruttore di tipi. È un semplice esempio di polimorfismo parametrico (limitato).
Abbiamo diversi tipi di polimorfismo nei linguaggi di programmazione. Il più naturale dei linguaggi OOP come Java è il polimorfismo del sottotipo , che è caratterizzato dal fatto che da X <: Y
e t : Y
, ne segue che t : Y
(IOW se un termine / variabile / parametro / qualunque abbia tipo X
che è un sottotipo di Y
, ha anche digitato Y
ed è utilizzabile in qualsiasi posto in cui sarebbe vero un Y
).
Un altro tipo di polimorfismo è polimorfismo parametrico , il che significa che alcuni tipi sono in realtà variabili di tipo, che rappresentano un segnaposto per qualsiasi tipo e sono istanziati a tipi reali durante il controllo ortografico. Il polimorfismo parametrico limitato indica che le variabili tpe possono essere vincolate per essere un sottotipo o supertipo di altro tipo. I generici offrono il polimorfismo parametrico in Java.
Ora, come giocano questi due insieme? Sembra naturale che un file di dipendenti possa essere letto come un file di cittadino, poiché ogni dipendente è naturalmente un cittadino (che viene catturato nel programma dalla relazione di sottotipo). Questo significa ReadableFile[Employee] <: ReadableFile[Citizen]
. Tuttavia, le regole per determinare i sottotipi non supportano questa conclusione - entrambi i tipi sono totalmente indipendenti.
È qui che entra in gioco la varianza. Se definiamo alcuni costruttori di tipi (in questo caso ReadableFile
) come co (contra-) variante, possiamo usare regole di inferenza come "se X <: Y
e T
è covariante, quindi T[X] <: T[Y]
". In effetti, questa regola supporta la conclusione di cui sopra.
I costruttori di tipo parametrico in Java sono sempre invarianti.
Come già notato, a volte possiamo andare avanti senza costruttori di varianti e ottenere lo stesso effetto. Nei parametri di funzione,
def foo(x : TCovar[Reader]) : Unit
con covariant TCovar
è funzionalmente equivalente a
def foo[A <: Reader](x : T[A]) : Unit
Questo non significa comunque che T
nella seconda definizione sia covariante.
Tuttavia, in funzione tipo di ritorno e tipi di variabile, questo trucco in genere non aiuta. Anche in questo caso, possiamo ottenere lo stesso effetto dei costruttori del tipo variant con l'aiuto di tipi esistenziali . Quindi
val foo : TCovar[Reader]
diventa
val foo : T[X] forSome { type X <: Reader }
Puoi vedere questo in azione in questo esempio . Come puoi vedere nella definizione di v4
e v5
, i costruttori di tipo covariante possono rendere il programma molto più semplice. Questa semplificazione si applica anche alle dichiarazioni dei parametri di funzione, motivo per cui i linguaggi di programmazione funzionale con polimorfismo di sottotipo utilizzano costruttori di tipo variante (almeno per i loro tipi di funzione).
Tuttavia, affinché un costruttore di tipi sia covariante, deve soddisfare alcune regole. Queste regole fanno sì che la List<T>
di Java e altre interfacce siano effettivamente invarianti, poiché forniscono sia la lettura che la scrittura dei dati del contenitore (che è tipica per la programmazione imperativa). Le strutture dati funzionali di Scala non funzionano in questo modo, quindi possono essere covarianti. È dimostrato anche nel esempio - NCov
non può essere covariante, perché fornisce read e scrivere l'accesso ai suoi dati interni. Credo che questo sia il motivo principale per cui i costruttori di tipo covariante non esistono in Java, che è la risposta alla tua domanda .
Ci scusiamo per aver mixato Java e Scala in questo post. Spero che gli esempi siano così facili da capire che non ha importanza.