Qual è la differenza tra <? super E> e <? estende E>?


147

Qual è la differenza tra <? super E>e <? extends E>?

Ad esempio, quando dai un'occhiata alla classe java.util.concurrent.LinkedBlockingQueuec'è la seguente firma per il costruttore:

public LinkedBlockingQueue(Collection<? extends E> c)

e per uno per il metodo:

public int drainTo(Collection<? super E> c)

Risposte:


182

Il primo dice che è "un tipo che è un antenato di E"; il secondo dice che è "un tipo che è una sottoclasse di E". (In entrambi i casi la stessa E va bene.)

Quindi il costruttore utilizza il ? extends Emodulo in modo da garantire che quando recupera i valori dalla raccolta, saranno tutti E o alcune sottoclassi (ovvero compatibili). Il drainTometodo sta cercando di inserire valori nella raccolta, quindi la raccolta deve avere un tipo di elemento E o una superclasse .

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

Parent extends Object
Child extends Parent

e a LinkedBlockingQueue<Parent>. Puoi costruire questo passaggio in un List<Child>che copierà tutti gli elementi in modo sicuro, perché ognuno Childè un genitore. Non è possibile passare a List<Object>perché alcuni elementi potrebbero non essere compatibili conParent .

Allo stesso modo puoi scaricare quella coda in a List<Object>perché ogni Parentè un Object... ma non puoi scaricarla in a List<Child>perché si List<Child>aspetta che tutti i suoi elementi siano compatibili Child.


25
+1. Questa è davvero la differenza pratica. si estende per recuperare, super per inserire.
Yishai,

1
@Jon cosa intendi per (in entrambi i casi la stessa E va bene.) Nel primo paragrafo?
Geek,

2
@Geek: voglio dire che se hai qualcosa di simile ? extends InputStreamo ? super InputStreampuoi usare un InputStreamcome argomento.
Jon Skeet,

Non ho mai avuto la spiegazione di PECS di Josh Block in Java efficace. Tuttavia @Yishai, questo è un modo utile per ricordare. Forse possiamo proporre un nuovo mnemonico, SAGE: Super -> Aggiungi / Ottieni -> Estendi
compilato il

Quindi, se l'ho letto bene, "<? Estende E>" richiede che "?" è una sottoclasse di "E" e "<? super E>" richiede che "E" sia una sottoclasse di "?", giusto?
El Suscriptor Justiciero,

130

Le ragioni 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 farlo?

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

Quest'ultima riga verrà compilata correttamente, ma se si esegue questo codice, è possibile ottenere un ArrayStoreException . Perché stai cercando di inserire 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 tipi di runtime. E questo perché le matrici sono quelli che chiamiamo tipi riutilizzabili . Ciò significa che in fase di esecuzione Java sa che questo array è stato effettivamente istanziato come un array di numeri interi a cui si accede semplicemente tramite un riferimento di tipo Number[].

Quindi, come puoi vedere, una cosa è il tipo reale dell'oggetto, 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 si chiama cancellazione del tipo . Ci sono buone ragioni per implementare generici come questo in Java, ma questa è una lunga storia, e deve fare, tra l'altro, con la compatibilità binaria con codice preesistente (vedi Come abbiamo i generici che abbiamo ).

Ma il punto importante qui è che dal momento che, in fase di esecuzione, non ci sono informazioni sul tipo, non c'è modo di garantire che non stiamo commettendo l'inquinamento del mucchio.

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 pollution

Se il compilatore Java non ti impedisce di farlo, neanche il sistema di tipi di runtime può impedirti, poiché al momento dell'esecuzione non è possibile determinare che questo elenco dovrebbe essere solo un elenco di numeri interi. Il runtime Java ti consente di inserire tutto ciò che desideri in questo elenco, quando dovrebbe contenere solo numeri interi, poiché quando è stato creato, è stato dichiarato come un elenco di numeri interi.

Pertanto, i progettisti di Java si sono assicurati di non poter ingannare il compilatore. Se non puoi ingannare il compilatore (come possiamo fare con gli array), non puoi nemmeno ingannare il sistema di tipi di runtime.

Pertanto, diciamo che i tipi generici non sono riutilizzabili .

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 si tenta di implementare lo stesso codice con raccolte generiche, non si riuscirà:

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

Otterresti errori del compilatore se provi a ...

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 usare due potenti funzionalità dei generici Java noti come covarianza e contravarianza.

covarianza

Con la covarianza puoi leggere elementi da una struttura, ma non puoi scrivere nulla al suo interno. 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 puoi leggere da myNums:

Number n = myNums.get(0); 

Perché puoi essere sicuro che qualunque sia l'elenco reale, può essere convertito in un numero (dopo tutto ciò che estende Number è un numero, giusto?)

Tuttavia, non ti è permesso inserire nulla in una struttura covariante.

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

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

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 effettiva dell'oggetto è un Elenco di oggetti e, attraverso la contraddizione, è possibile inserire i Numeri in esso, fondamentalmente perché tutti i numeri hanno Oggetto come loro antenato comune. Come tale, tutti i numeri sono oggetti e quindi questo è valido.

Tuttavia, non puoi leggere nulla di sicuro da questa struttura contraddittoria supponendo che otterrai un numero.

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

Come puoi vedere, se il compilatore ti consentisse di scrivere questa riga, otterrai una ClassCastException in fase di esecuzione.

Principio Get / Put

Come tale, utilizzare la covarianza quando si intende estrarre valori generici da una struttura, utilizzare la contraddizione quando si intende solo inserire valori generici in una struttura e utilizzare il tipo generico esatto quando si intende fare entrambe le cose.

Il miglior esempio che ho è il seguente che copia qualsiasi tipo di numero da un elenco a un altro elenco. Riceve solo oggetti dalla fonte e mette solo oggetti nel bersaglio.

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

Grazie ai poteri della covarianza e della contraddizione 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);

27
Questa risposta dovrebbe andare in cima. Bella spiegazione.
Suresh Atta,

1
@edwindalorzo, c'è un piccolo errore di battitura che vuoi correggere in Contravarianza. Dici List<Object> myObjs = new List<Object();(che manca la chiusura >per il secondo Object).

Esempi fantastici, facili e chiari di questi concetti sottili!
db1234,

Qualcosa che potresti aggiungere per aiutare gli altri a ricordare le cose. Quando si desidera chiamare un metodo da una super classe, si utilizza super.methodName. Quando si usa <? super E>, significa "qualcosa nella superdirezione" invece di qualcosa nella extendsdirezione. Esempio: Objectè nella superdirezione di Number(poiché è una super classe) ed Integerè nella extendsdirezione (poiché si estende Number).
BrainStorm.exe

59

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

<? super E>definisce Eil limite inferiore: " Epuò essere eseguito il cast a questo".


6
Questo è uno dei migliori riassunti semplici / pratici della differenza che ho visto.
JAB

1
Da decenni ormai (con OOP) ho combattuto una inversione istintiva delle nozioni "superiore" e "inferiore". Aggravante! Per me, Objectè intrinsecamente una classe di livello inferiore, nonostante sia la posizione come superclasse finale (e disegnata verticalmente in UML o alberi ereditari simili). Non sono mai stato in grado di annullare questo nonostante eoni di tentativi.

3
@ tgm1024 "superclasse" e "sottoclasse" devono creare molti problemi.
David Moles,

@DavidMoles, perché? Chiaramente non stai seguendo quello che sto dicendo. "superclasse" è simile a "superset"; la nozione di specializzazione è una riduzione dell'applicabilità nell'ambito della relazione IS-A. Apple è un frutto. Fruit (superclasse) è un superset che include Apple (sottoclasse) come sottoinsieme. Quella relazione verbale va bene. Quello che sto dicendo scompone è l'idea che "superiore" e "inferiore" hanno mappature intrinseche a "superset" e "subset". Upper e Lower dovrebbero essere evitati come termini in OO.

1
@ tgm1024 "Super-" deriva dal super latino "sopra, sopra" e "sub-" dal latino sotto "sotto, sotto". Vale a dire, etimologicamente, super è su e sub è su.
David Moles,

12

Proverò a rispondere a questa domanda. 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 utilizza i valori generici dall'oggetto, è necessario utilizzare 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/


12

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

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


risposta breve e dolce.
Chirag Soni,

7

Potresti voler google per i termini contravarianza ( <? super E>) e covarianza ( <? extends E>). Ho scoperto che la cosa più utile nel comprendere i generici è stata per me capire la firma del metodo di Collection.addAll:

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

Proprio come vorresti poter aggiungere Stringa a List<Object>:

List<Object> lo = ...
lo.add("Hello")

Dovresti anche essere in grado di aggiungere una List<String>(o qualsiasi raccolta di String) tramite il addAllmetodo:

List<String> ls = ...
lo.addAll(ls)

Tuttavia, dovresti capire che a List<Object>e a List<String>non sono equivalenti e che la seconda non è una sottoclasse della prima. Ciò che serve è il concetto di un parametro di tipo covariante , ovvero il <? extends T>bit.

Una volta che hai questo, è semplice pensare a scenari in cui vuoi anche la contraddizione (controlla l' Comparableinterfaccia).


4

Prima della risposta; Per favore, sii chiaro

  1. Generics compila solo la funzione di tempo 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 il tipo safty.

Esempio:

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

Spero che questo ti aiuti a capire il carattere jolly più chiaro.

//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<A> list_A = new ArrayList<A>();
        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<B> list_B = new ArrayList<B>();
        //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<A>
        List<? super A> list_4__A_AND_SuperClass_A = new ArrayList<A>();
                        list_4__A_AND_SuperClass_A = new ArrayList<Object>();
                      //list_4_A_AND_SuperClass_A = new ArrayList<B>(); CE B is SubClass of A
                      //list_4_A_AND_SuperClass_A = new ArrayList<String>(); CE String is not super of A  
        List<? extends A> list_4__A_AND_SubClass_A = new ArrayList<A>();
                          list_4__A_AND_SubClass_A = new ArrayList<B>();
                        //list_4__A_AND_SubClass_A = new ArrayList<Object>(); CE Object is SuperClass of A


        //CE; super reference, only accepts list of A or its super classes.
        //List<? super A> list_4__A_AND_SuperClass_A = new ArrayList<String>(); 

        //CE; extends reference, only accepts list of A or its sub classes.
        //List<? extends A> list_4__A_AND_SubClass_A = new ArrayList<Object>();

        //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<Apple> apples = new ArrayList<Apple>();
        //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<A>) list_4__A_AND_SubClass_A;
        list_A.add(aObj);
        list_A.add(bObj);

        list_B = (List<B>) list_4__A_AND_SubClass_A;
        //list_B.add(aObj); compilation error
        list_B.add(bObj);

        private Map<Class<? extends Animal>, 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<Donkey>, List<Mouse>, List<Pikachu>, assuming Donkey, Mouse, and Pikachu were all sub classes 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<Animal> animalList = animalListMap.get(animalObject);
                animalList.add(animalObject);      


            } 
    }

    }
}



1
Buona spiegazione con il codice. Ma se avessi usato commenti nel codice al di fuori del blocco di codice, sarebbe meglio per la visualizzazione e più leggibili.
Prabhu,

3

Un carattere jolly con un limite superiore assomiglia a "? Extends Type" e rappresenta la famiglia di tutti i tipi che sono sottotipi di Type, tipo Type incluso. Il tipo è chiamato limite superiore.

Un carattere jolly con un limite inferiore assomiglia a "? Super Type" e rappresenta la famiglia di tutti i tipi che sono supertipi di Tipo, tipo Type incluso. Il tipo è chiamato limite inferiore.


1

Hai una classe Parent e una classe Child ereditate dalla classe Parent. La classe Parent è ereditata da un'altra classe chiamata GrandParent Class. L'ordine di eredità è quindi GrandParent> Parent> Child. Ora <? extends Parent>: accetta la classe Parent o la classe Child <? super Parent>: accetta la classe Parent o la classe GrandParent

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.