Qual è la differenza tra e ?

Qual è la differenza tra e ?

Per esempio quando java.util.concurrent.LinkedBlockingQueue un’occhiata alla class java.util.concurrent.LinkedBlockingQueue c’è la seguente firma per il costruttore:

 public LinkedBlockingQueue(Collection c) 

e per uno per il metodo:

 public int drainTo(Collection c) 

Il primo dice che è “un tipo che è un antenato di E”; il secondo dice che è “un tipo che è una sottoclass di E”. (In entrambi i casi E va bene.)

Quindi il costruttore usa il ? extends E ? extends E forma ? extends E modo da garantire che quando recupera i valori dalla raccolta, saranno tutti E o una sottoclass (cioè è compatibile). Il metodo drainTo sta cercando di inserire valori nella raccolta, quindi la raccolta deve avere un tipo di elemento di E o una superclass .

Ad esempio, supponiamo di avere una gerarchia di classi come questa:

 Parent extends Object Child extends Parent 

e un LinkedBlockingQueue . Puoi build questo passaggio in una List che copierà tutti gli elementi in modo sicuro, perché ogni Child è un genitore. Non è stato ansible passare in un List perché alcuni elementi potrebbero non essere compatibili con Parent .

Allo stesso modo puoi scaricare quella coda in un List perché ogni Parent è un Object … ma non puoi scaricarlo in un List perché l’ List aspetta che tutti i suoi elementi siano compatibili con Child .

Le ragioni di ciò sono basate su come Java implementa i generici.

Un esempio di array

Con gli array puoi farlo (gli array sono covarianti)

 Integer[] myInts = {1,2,3,4}; Number[] myNumber = myInts; 

Ma cosa succederebbe se provassi a fare questo?

 myNumber[0] = 3.14; //attempt of heap pollution 

Quest’ultima riga potrebbe essere compilata correttamente, ma se si esegue questo codice, è ansible ottenere ArrayStoreException . Perché stai cercando di mettere un doppio in un array intero (indipendentemente dall’accesso tramite un riferimento numerico).

Ciò significa che puoi ingannare il compilatore, ma non puoi ingannare il sistema di tipo runtime. E questo è così perché gli array sono quelli che 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’object, e un’altra cosa è il tipo di riferimento che usi per accedervi, giusto?

Il problema con Java Generics

Ora, il problema con i tipi generici Java è che le informazioni sul tipo vengono scartate dal compilatore e non sono disponibili in fase di esecuzione. Questo processo è chiamato cancellazione di tipo . 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 dal momento che a runtime non ci sono informazioni sul tipo, non c’è modo di garantire che non stiamo commettendo inquinamento da cumulo.

Per esempio,

 List myInts = new ArrayList(); myInts.add(1); myInts.add(2); List myNums = myInts; //compiler error myNums.add(3.14); //heap pollution 

Se il compilatore Java non ti impedisce di farlo, il sistema di tipo runtime non può fermarti, perché non c’è modo, in fase di esecuzione, di determinare che questa lista doveva 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.

In quanto 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.

In quanto tali, diciamo che i tipi generici non sono ri-soggettibili .

Evidentemente, ciò ostacolerebbe il polimorfismo. Considera il seguente esempio:

 static long sum(Number[] numbers) { long summation = 0; for(Number number : numbers) { summation += number.longValue(); } return summation; } 

Ora puoi usarlo 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 tenti di implementare lo stesso codice con le raccolte generiche, non avrai successo:

 static long sum(List numbers) { long summation = 0; for(Number number : numbers) { summation += number.longValue(); } return summation; } 

Otterrai gli errori del compilatore se provi a …

 List myInts = asList(1,2,3,4,5); List myLongs = asList(1L, 2L, 3L, 4L, 5L); List 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 puoi leggere elementi da una struttura, ma non puoi scrivere nulla in essa. Tutte queste sono dichiarazioni valide.

 List< ? extends Number> myNums = new ArrayList(); List< ? extends Number> myNums = new ArrayList(); List< ? extends Number> myNums = new ArrayList(); 

E puoi leggere da myNums :

 Number n = myNums.get(0); 

Perché puoi essere sicuro che qualunque cosa contenga l’elenco reale, può essere trasmesso a un numero (dopotutto qualsiasi cosa che estende Numero è un numero, giusto?)

Tuttavia, non è consentito inserire nulla in una struttura covariante.

 myNumst.add(45L); //compiler error 

Ciò non sarebbe consentito, perché Java non può garantire quale sia il tipo effettivo dell’object 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 la controvarianza puoi fare il contrario. Puoi mettere le cose in una struttura generica, ma non puoi leggerne.

 List myObjs = new List(); myObjs.add("Luke"); myObjs.add("Obi-wan"); List< ? super Number> myNums = myObjs; myNums.add(10); myNums.add(3.14); 

In questo caso, la natura effettiva dell’object è un elenco di oggetti e, grazie alla contravarianza, è ansible inserire i numeri in esso, in pratica perché tutti i numeri hanno Object come antenato comune. In quanto tale, tutti i numeri sono oggetti, e quindi questo è valido.

Tuttavia, non è ansible leggere in modo sicuro nulla da questa struttura controvariante presumendo che si otterrà un numero.

 Number myNum = myNums.get(0); //compiler-error 

Come puoi vedere, se il compilatore ti ha permesso di scrivere questa riga, otterrai un ClassCastException in fase di runtime.

Prendi / Inserisci principio

Come tale, usa la covarianza quando intendi solo prendere valori generici da una struttura, usa la controvarianza quando intendi solo inserire valori generici in una struttura e usare il tipo generico esatto quando intendi fare entrambe le cose.

Il miglior esempio che ho è il seguente che copia qualsiasi tipo di numero da una lista in un’altra lista. Riceve solo gli oggetti dalla fonte e mette solo gli oggetti nella destinazione.

 public static void copy(List< ? extends Number> source, List< ? super Number> target) { for(Number number : source) { target(number); } } 

Grazie ai poteri di covarianza e controvarianza funziona per un caso come questo:

 List myInts = asList(1,2,3,4); List myDoubles = asList(3.14, 6.28); List myObjs = new ArrayList(); copy(myInts, myObjs); copy(myDoubles, myObjs); 

< ? extends E> < ? extends E> definisce E come limite superiore: “Questo può essere lanciato su E “.

< ? super E> < ? super E> definisce E come limite inferiore: ” E può essere lanciato su questo”.

Ho intenzione di provare a rispondere a questo. Ma per ottenere una risposta davvero buona dovresti controllare il libro di Joshua Bloch, Effective Java (2nd Edition). Descrive il PECS mnemonico, che sta per “Producer Extends, Consumer Super”.

L’idea è che se il codice sta consumando i valori generici dall’object, allora dovresti usare extends. ma se stai producendo nuovi valori per il tipo generico dovresti usare super.

Quindi per esempio:

 public void pushAll(Iterable< ? extends E> src) { for (E e: src) push(e); } 

E

 public void popAll(Collection< ? super E> dst) { while (!isEmpty()) dst.add(pop()) } 

Ma davvero dovresti dare un’occhiata a questo libro: http://java.sun.com/docs/books/effective/

< ? super E> < ? super E> indica any object including E that is parent of E

< ? extends E> < ? extends E> indica any object including E that is child of E .

Potresti voler google per i termini controvarianza ( < ? super E> ) e covarianza ( < ? extends E> ). Ho trovato che la cosa più utile quando comprendevo i generici era per me capire la firma del metodo di Collection.addAll :

 public interface Collection { public boolean addAll(Collection< ? extends T> c); } 

Proprio come vorresti essere in grado di aggiungere una String ad un List :

 List lo = ... lo.add("Hello") 

Dovresti anche essere in grado di aggiungere un List (o qualsiasi raccolta di String s) tramite il metodo addAll :

 List ls = ... lo.addAll(ls) 

Tuttavia, dovresti capire che un List e un List non sono equivalenti e che non è il secondo una sottoclass del primo. Quello che serve è il concetto di un parametro di tipo covariante – cioè il < ? extends T> < ? extends T> bit.

Una volta che hai questo, è semplice pensare a scenari in cui vuoi anche la contravarianza (controlla l’interfaccia Comparable ).

Prima della risposta; Per favore, sii chiaro

  1. Generics utilizza solo la funzione di compilazione per garantire TYPE_SAFETY, non sarà disponibile durante RUNTIME.
  2. Solo un riferimento con Generics forzerà la sicurezza del tipo; se il riferimento non viene dichiarato con generici, funzionerà senza tipo safty.

Esempio:

 List stringList = new ArrayList(); stringList.add(new Integer(10)); // will be successful. 

Spero che questo ti aiuterà a capire la wildcard più chiara.

 //NOTE CE - Compilation Error // 4 - For class A {} class B extends A {} public class Test { public static void main(String args[]) { A aObj = new A(); B bObj = new B(); //We can add object of same type (A) or its subType is legal List list_A = new ArrayList(); list_A.add(aObj); list_A.add(bObj); // A aObj = new B(); //Valid //list_A.add(new String()); Compilation error (CE); //can't add other type A aObj != new String(); //We can add object of same type (B) or its subType is legal List list_B = new ArrayList(); //list_B.add(aObj); CE; can't add super type obj to subclass reference //Above is wrong similar like B bObj = new A(); which is wrong list_B.add(bObj); //Wild card (?) must only come for the reference (left side) //Both the below are wrong; //List< ? super A> wildCard_Wrongly_Used = new ArrayList< ? super A>(); //List< ? extends A> wildCard_Wrongly_Used = new ArrayList< ? extends A>(); //Both < ? extends A>; and < ? super A> reference will accept = new ArrayList List< ? super A> list_4__A_AND_SuperClass_A = new ArrayList(); list_4__A_AND_SuperClass_A = new ArrayList(); //list_4_A_AND_SuperClass_A = new ArrayList(); CE B is SubClass of A //list_4_A_AND_SuperClass_A = new ArrayList(); CE String is not super of A List< ? extends A> list_4__A_AND_SubClass_A = new ArrayList(); list_4__A_AND_SubClass_A = new ArrayList(); //list_4__A_AND_SubClass_A = new ArrayList(); CE Object is SuperClass of A //CE; super reference, only accepts list of A or its super classs. //List< ? super A> list_4__A_AND_SuperClass_A = new ArrayList(); //CE; extends reference, only accepts list of A or its sub classs. //List< ? extends A> list_4__A_AND_SubClass_A = new ArrayList(); //With super keyword we can use the same reference to add objects //Any sub class object can be assigned to super class reference (A) list_4__A_AND_SuperClass_A.add(aObj); list_4__A_AND_SuperClass_A.add(bObj); // A aObj = new B(); //list_4__A_AND_SuperClass_A.add(new Object()); // A aObj != new Object(); //list_4__A_AND_SuperClass_A.add(new String()); CE can't add other type //We can't put anything into "? extends" structure. //list_4__A_AND_SubClass_A.add(aObj); compilation error //list_4__A_AND_SubClass_A.add(bObj); compilation error //list_4__A_AND_SubClass_A.add(""); compilation error //The Reason is below //List apples = new ArrayList(); //List< ? extends Fruit> fruits = apples; //fruits.add(new Strawberry()); THIS IS WORNG :) //Use the ? extends wildcard if you need to retrieve object from a data structure. //Use the ? super wildcard if you need to put objects in a data structure. //If you need to do both things, don't use any wildcard. //Another Solution //We need a strong reference(without wild card) to add objects list_A = (ArrayList) list_4__A_AND_SubClass_A; list_A.add(aObj); list_A.add(bObj); list_B = (List) list_4__A_AND_SubClass_A; //list_B.add(aObj); compilation error list_B.add(bObj); private Map, List< ? extends Animal>> animalListMap; public void registerAnimal(Class< ? extends Animal> animalClass, Animal animalObject) { if (animalListMap.containsKey(animalClass)) { //Append to the existing List /* The ? extends Animal is a wildcard bounded by the Animal class. So animalListMap.get(animalObject); could return a List, List, List, assuming Donkey, Mouse, and Pikachu were all sub classs of Animal. However, with the wildcard, you are telling the compiler that you don't care what the actual type is as long as it is a sub type of Animal. */ //List< ? extends Animal> animalList = animalListMap.get(animalObject); //animalList.add(animalObject); //Compilation Error because of List< ? extends Animal> List animalList = animalListMap.get(animalObject); animalList.add(animalObject); } } } } 

Un carattere jolly con un limite superiore appare come “? Estende tipo” e rappresenta la famiglia di tutti i tipi che sono sottotipi di Tipo, tipo Tipo incluso. Il tipo è chiamato il limite superiore.

Un carattere jolly con un limite inferiore appare come “supertipo” e rappresenta la famiglia di tutti i tipi che sono supertipi di tipo, incluso il tipo di tipo. Il tipo è chiamato il limite inferiore.

Hai una class Parent e una class Child ereditata dalla class Parent. La class padre è ereditata da un’altra class chiamata GrandParent Class.So Order of inheritence è GrandParent> Parent> Child. Ora, < ? estende Parent> – Questo accetta la class Parent o la class Child < ? super Parent>: accetta la class Parent o la class GrandParent