Le ragioni di questo sono basate su come Java implementa i generici.
Un Array Esempio
Con gli array si può fare questo (gli array sono covariante come altri hanno spiegato)
Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;
Ma, che cosa accadrebbe se si tenta di fare questo?
Number[0] = 3.14; //attempt of heap pollution
Questa ultima riga sarebbe compilare bene, ma se si esegue questo codice, si potrebbe ottenere un ArrayStoreException
. Perché stai cercando di mettere un doppio in un array intero (indipendentemente dall'accesso tramite un riferimento numerico).
Ciò significa che è possibile ingannare il compilatore, ma non è possibile ingannare il sistema di tipo runtime. E questo è così perché gli array sono quelli che noi chiamiamo tipi reificabili. Ciò significa che in fase di esecuzione Java sa che questa matrice è stata effettivamente istanziata come una matrice di numeri interi a cui semplicemente si accede tramite un riferimento di tipo Number[]
.
Quindi, come puoi vedere, una cosa è il tipo effettivo dell'oggetto, un'altra cosa è il tipo di riferimento che usi per accedervi, giusto?
Il problema con Java Generics
Ora, il problema con Java tipi generici è che le informazioni sul tipo viene scartato dal compilatore e non è disponibile in fase di esecuzione. Questo processo è chiamato type erasure. Ci sono buone ragioni per implementare generici come questo in Java, ma questa è una lunga storia e ha a che fare con la compatibilità binaria con il codice preesistente.
Ma il punto importante qui è che, in fase di esecuzione non ci sono informazioni sul tipo, non c'è modo di garantire che non stiamo commettendo inquinamento da cumulo.
Per esempio,
List<Integer> myInts = new ArrayList<Integer>();
myInts.add(1);
myInts.add(2);
List<Number> myNums = myInts; //compiler error
myNums.add(3.14); //heap polution
Se il compilatore Java non ti impedisce di fare questo, il sistema di tipo runtime non può fermare neanche, perché non c'è modo, in fase di esecuzione, per determinare che questo elenco è stato dovrebbe essere solo una lista di interi. Il runtime Java ti consente di inserire tutto ciò che vuoi in questo elenco, quando deve contenere solo numeri interi, perché quando è stato creato, è stato dichiarato come un elenco di numeri interi.
Come tale, i progettisti di Java hanno fatto in modo che non si potesse ingannare il compilatore. Se non puoi ingannare il compilatore (come possiamo fare con gli array) non puoi ingannare neanche il sistema di tipo runtime.
Come tale, diciamo che i tipi generici sono non riscrivibili.
Evidentemente, questo ostacolerebbe il polimorfismo.Si consideri il seguente esempio:
static long sum(Number[] numbers) {
long summation = 0;
for(Number number : numbers) {
summation += number.longValue();
}
return summation;
}
Ora si potrebbe usare in questo modo:
Integer[] myInts = {1,2,3,4,5};
Long[] myLongs = {1L, 2L, 3L, 4L, 5L};
Double[] myDoubles = {1.0, 2.0, 3.0, 4.0, 5.0};
System.out.println(sum(myInts));
System.out.println(sum(myLongs));
System.out.println(sum(myDoubles));
Ma se si tenta di implementare lo stesso codice con le collezioni generiche, non si avrà successo:
static long sum(List<Number> numbers) {
long summation = 0;
for(Number number : numbers) {
summation += number.longValue();
}
return summation;
}
Si otterrebbero degli errori del compilatore se si tenta di ...
List<Integer> myInts = asList(1,2,3,4,5);
List<Long> myLongs = asList(1L, 2L, 3L, 4L, 5L);
List<Double> myDoubles = asList(1.0, 2.0, 3.0, 4.0, 5.0);
System.out.println(sum(myInts)); //compiler error
System.out.println(sum(myLongs)); //compiler error
System.out.println(sum(myDoubles)); //compiler error
La soluzione è imparare a utilizzare due potenti funzionalità dei generici Java noti come covarianza e controvarianza.
covarianza
Con covarianza è possibile leggere gli elementi da una struttura, ma non si può scrivere nulla in esso. Tutte queste sono dichiarazioni valide.
List<? extends Number> myNums = new ArrayList<Integer>();
List<? extends Number> myNums = new ArrayList<Float>()
List<? extends Number> myNums = new ArrayList<Double>()
E si può leggere da myNums
:
Number n = myNums.get(0);
Perché si può essere sicuri che tutto ciò che ha l'elenco contiene, può essere upcasted ad un numero (dopo tutto tutto ciò che si estende numero è un numero , giusto?)
Tuttavia, non è consentito inserire nulla in una struttura covariante.
myNumst.add(45L); //compiler error
questo non sarebbe consentito, perché Java non può garantire quello che è il tipo reale dell'oggetto nella struttura generica. Può essere qualsiasi cosa che estende Numero, ma il compilatore non può essere sicuro. Quindi puoi leggere, ma non scrivere.
controvarianza
Con controvarianza si può fare il contrario. Puoi mettere le cose in una struttura generica, ma non puoi leggerne.
List<Object> myObjs = new List<Object();
myObjs.add("Luke");
myObjs.add("Obi-wan");
List<? super Number> myNums = myObjs;
myNums.add(10);
myNums.add(3.14);
In questo caso, la natura reale dell'oggetto è una lista di oggetti, e attraverso controvarianza, è possibile mettere i numeri in esso, fondamentalmente perché tutti i numeri abbiano oggetto come il loro antenato comune. In quanto tale, tutti i numeri sono oggetti, e quindi questo è valido.
Tuttavia, non è possibile leggere in modo sicuro nulla da questa struttura controvariante presumendo che si otterrà un numero.
Number myNum = myNums.get(0); //compiler-error
Come si può vedere, se il compilatore ha permesso di scrivere questa linea, si otterrebbe un ClassCastException in fase di esecuzione.
Get/Put Principio
Come tale, utilizzare covarianza quando hai solo intenzione di prendere valori generici di una struttura, utilizzare controvarianza quando si intende solo per mettere i valori generici in una struttura e utilizzare l'esatto generica scrivi quando intendi fare entrambe le cose.
L'esempio migliore che ho è il seguente che copia qualsiasi tipo di numero da una lista in un'altra lista. Solo riceve elementi dalla fonte e solo mette gli oggetti nel destino.
public static void copy(List<? extends Number> source, List<? super Number> destiny) {
for(Number number : source) {
destiny.add(number);
}
}
Grazie ai poteri di covarianza e controvarianza Questo funziona per un caso come questo:
List<Integer> myInts = asList(1,2,3,4);
List<Double> myDoubles = asList(3.14, 6.28);
List<Object> myObjs = new ArrayList<Object>();
copy(myInts, myObjs);
copy(myDoubles, myObjs);
Ci sono quelli che sostengono che gli array che consentono di fare che è stata una cattiva idea. – Jeffrey
Tipo di cancellazione e generici, non raccolte, sono la ragione. – duffymo
La risposta breve è "i contenitori generici non sono matrici". La risposta più lunga, come suggerito da Duffymo alreyad, è "cancellazioni": http://code.stephenmorley.org/articles/java-generics-type-erasure/ – paulsm4