2009-09-02 18 views
111

¿Cuál es la diferencia entre <? super E> y <? extends E>?¿Cuál es la diferencia entre <? super E> y <? extends E>?

Por ejemplo cuando se echa un vistazo a la clase java.util.concurrent.LinkedBlockingQueue existe la siguiente firma para el constructor:

public LinkedBlockingQueue(Collection<? extends E> c) 

y uno para el método:

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

Respuesta

143

La primera dice que es " algún tipo que es un antepasado de E "; el segundo dice que es "algún tipo que es una subclase de E". (En ambos casos E en sí está bien.)

Así que el constructor utiliza la forma ? extends E lo que garantiza que cuando se Obtiene valores de la colección, todos ellos serán E o alguna subclase (es decir, es compatible). El método drainTo intenta poner los valores en la colección, por lo que la colección debe tener un tipo de elemento de Eo una superclase.

A modo de ejemplo, supongamos que tiene una jerarquía de clases como esto:

Parent extends Object 
Child extends Parent 

y una LinkedBlockingQueue<Parent>. Puede construir este pase en un List<Child> que copiará todos los elementos de forma segura, porque cada Child es uno de los elementos principales. No se pudo pasar un List<Object> porque algunos elementos podrían no ser compatibles con Parent.

Del mismo modo se puede drenar esa cola en un List<Object> porque cada Parent es un Object ... pero no se puede drenar en un List<Child> porque el List<Child> espera que todos sus elementos sean compatibles con Child.

+16

+1. Esa es realmente la diferencia práctica. se extiende a buscar, super para insertar. – Yishai

+0

Muchas gracias. Gran explicación! –

+0

@Jon ¿a qué te refieres con (en ambos casos, E mismo está bien) en el primer párrafo? – Geek

7

Usted podría want to google for the termscontravarianza (<? super E>) y covarianza (<? extends E>). He encontrado que lo más útil cuando genéricos comprender era para mí entender el método de firma de Collection.addAll:

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

Así como te gustaría ser capaz de añadir un String a un List<Object>:

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

también debe ser capaz de añadir un List<String> (o cualquier colección de String s) a través del método de addAll:

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

Sin embargo, debe tener en cuenta que List<Object> y List<String> no son equivalentes y que esta última tampoco es una subclase de la primera. Lo que se necesita es el concepto de un parámetro de tipo covariante, es decir, el bit <? extends T>.

Una vez que tenga esto, es fácil de imaginar escenarios en los que desee contravarianza también (marque la interfaz Comparable).

3

Un comodín con un límite superior se ve como "? Extends Tipo" y representa la familia de todos los tipos que son subtipos de Tipo, tipo de tipo que se incluye. Tipo se llama límite superior.

Un comodín con un límite inferior parece "? Super Type" y representa la familia de todos los tipos que son supertipos de tipo, tipo de tipo que se incluye. Tipo se llama límite inferior.

38

<? extends E> define E como el límite superior: "Esto se puede convertir a E".

<? super E> define E como el límite inferior: "E se puede convertir a esto".

+4

Este es uno de los mejores resúmenes simples/prácticos de la diferencia que he visto. – JAB

+1

Por *** décadas *** ahora (con OOP) He estado luchando contra una inversión instintiva de las nociones "superior" e "inferior". ¡Agravante! Para mí, 'Object' es intrínsecamente una clase de nivel inferior, a pesar de su posición como la última superclase (y dibujada verticalmente en UML o árboles de herencia similares). Nunca he podido deshacer esto a pesar de eones de intentarlo. –

+1

@ tgm1024 "superclase" y "subclase" deben causarle muchos problemas. –

10

Voy a tratar de responder esto. Pero para obtener una muy buena respuesta, debe consultar el libro de Joshua Bloch, Effective Java (2nd Edition). Él describe el PECS mnemotécnico, que significa "Producer Extends, Consumer Super".

La idea es que si el código consume los valores genéricos del objeto, entonces debe usar extends. pero si está produciendo nuevos valores para el tipo genérico, debe usar super.

Así, por ejemplo:

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

Y

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

Pero en realidad que debe salir de este libro: http://java.sun.com/docs/books/effective/

77

Las razones de esto se basan en cómo Java implementa los genéricos.

Un matrices Ejemplo

Con los arreglos que pueden hacer esto (arrays son covariantes)

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

Pero, ¿qué pasaría si intenta hacer esto?

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

Esta última línea se compilará bien, pero si se ejecuta este código, se podría obtener una ArrayStoreException. Porque intenta poner un doble en una matriz de enteros (independientemente de que se acceda a través de una referencia numérica).

Esto significa que puede engañar al compilador, pero no puede engañar al sistema del tipo de tiempo de ejecución. Y esto es así porque las matrices son lo que llamamos tipos confirmables. Esto significa que, en tiempo de ejecución, Java sabe que esta matriz se instancia realmente como una matriz de enteros a los que simplemente se accede mediante una referencia de tipo Number[].

Entonces, como puede ver, una cosa es el tipo real del objeto, y otra cosa es el tipo de referencia que utiliza para acceder a ella, ¿no?

El problema con Java Generics

Ahora, el problema con los tipos genéricos de Java es que la información de tipo se descarta por el compilador y no está disponible en tiempo de ejecución. Este proceso se llama type erasure. Hay buenas razones para implementar genéricos como este en Java, pero esa es una historia larga, y tiene que ver con la compatibilidad binaria con el código preexistente.

Pero el punto importante aquí es que, dado que, en el tiempo de ejecución no hay información de tipo, no hay forma de garantizar que no estamos cometiendo contaminación acumulativa.

Por ejemplo,

List<Integer> myInts = new ArrayList<Integer>(); 
myInts.add(1); 
myInts.add(2); 

List<Number> myNums = myInts; //compiler error 
myNums.add(3.14); //heap pollution 

Si el compilador Java no le impide hacer esto, el sistema de tipos en tiempo de ejecución no se puede parar o bien, porque no hay manera, en tiempo de ejecución, para determinar que esta lista era se supone que es una lista de enteros solamente. El tiempo de ejecución de Java le permite colocar lo que desee en esta lista, cuando solo debe contener enteros, porque cuando se creó, se declaró como una lista de enteros.

Como tal, los diseñadores de Java se aseguraron de que no se puede engañar al compilador. Si no puede engañar al compilador (como podemos hacer con las matrices) tampoco puede engañar al sistema de tipo de tiempo de ejecución.

Como tal, decimos que los tipos genéricos son no no reificables.

Evidentemente, esto obstaculizaría el polimorfismo. Consideremos el siguiente ejemplo:

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

Ahora se puede utilizar de esta manera:

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)); 

Pero si se intenta poner en práctica el mismo código con colecciones genéricas, que no tendrá éxito:

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

Obtendrá errores de compilación si intenta ...

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 solución es aprender a usar dos poderosas características de los genéricos de Java conocidos como covarianza y contravarianza.

covarianza

Con covarianza se puede leer los artículos en una estructura, pero no se puede escribir nada en él. Todas estas son declaraciones válidas.

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

Y se puede leer desde myNums:

Number n = myNums.get(0); 

Debido a que usted puede estar seguro de que cualquiera que sea la lista real contiene, se puede upcasted a un número (después de todo, cualquier cosa que se extiende número es un número , ¿no?)

Sin embargo, no está permitido poner nada en una estructura covariante.

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

Esto no se permitiría, porque Java no puede garantizar cuál es el tipo real del objeto en la estructura genérica. Puede ser cualquier cosa que extienda Number, pero el compilador no puede estar seguro. Para que pueda leer, pero no escribir.

contravarianza

Con contravarianza puede hacer lo contrario. Puede poner las cosas en una estructura genérica, pero no puede leer desde allí.

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); 

En este caso, la naturaleza real del objeto es una lista de objetos y, a través de contravarianza, puede poner los números en ella, básicamente porque todos los números tienen como objeto su ancestro común. Como tal, todos los números son objetos, y por lo tanto esto es válido.

Sin embargo, no puede leer con seguridad nada de esta estructura contravariante suponiendo que obtendrá un número.

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

Como se puede ver, si el compilador permitió a escribir esta línea, se llega a un ClassCastException en tiempo de ejecución.

Obtener/Poner Principio

Como tal, el uso de covarianza cuando sólo se van a tomar valores genéricos de una estructura, utilice contravarianza cuando sólo se van a poner los valores genéricos en una estructura y el uso de los genéricos exacta escriba cuando tenga la intención de hacer ambas cosas.

El mejor ejemplo que tengo es el siguiente que copia cualquier tipo de números de una lista a otra. Solo obtiene elementos de la fuente, y solo pone elementos en el destino.

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

Gracias a los poderes de la covarianza y contravarianza Esto funciona para un caso como este:

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); 
+15

Esta respuesta debería toparse con la parte superior. Buena explicación. –

+1

@edwindalorzo, hay un pequeño error tipográfico que desea corregir en Contravariance. Usted dice 'List myObjs = new List ' para el segundo 'Object'). –

+0

¡Ejemplos fantásticos, fáciles y claros de estos conceptos sutiles! – db1234

4

Antes de la respuesta; Tenga en cuenta que

  1. genéricos solo compilan la función de tiempo para garantizar TYPE_SAFETY, no estará disponible durante RUNTIME.
  2. Solo una referencia con Generics forzará la seguridad del tipo; si la referencia no se declara con genéricos, funcionará sin tipo de seguridad.

Ejemplo:

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

Hope esto le ayudará a entender comodín más clara.

//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);  


      } 
    } 

    } 
} 
+0

gracias! ... cosas geniales – Ris

+0

@Dana También vea http://stackoverflow.com/questions/15255929/generics-wildcards-assigning-super-class-object-to-a-subclass-object-list –

+0

Mi respuesta en Clase genérica tipo http://stackoverflow.com/questions/462297/how-to-use-classt-in-java/19545123#19545123 –

6

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

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

Cuestiones relacionadas