2009-02-26 10 views
10

Duplicar posibles:
what is the difference between ‘super’ and ‘extends’ in Java GenericsGenéricos ..? Super T

A)

List<? super Shape> shapeSuper = new ArrayList<Shape>(); 

shapeSuper.add(new Square());  //extends from SHAP 
shapeSuper.add(new DoubleSquare()); //extends from SQ 
shapeSuper.add(new TripleSquare()); //extends from DS 
shapeSuper.add(new Rectangle()); //extends from SHAP 
shapeSuper.add(new Circle());  //extends from SHAP 

for (Object object : shapeSuper) { ... } 

Por qué debe ser la iteración de objetos cuando puedo agregar sólo la forma y sus derivados ?

B)

List<? super Shape> shapeSuper = new ArrayList<Object>(); 

shapeSuper.add(new Object()); //compilation error 

¿Por qué desprende de la línea por encima de un error de compilación?

+0

Por curiosidad, ¿cuál es el beneficio de hacer esto sobre definir una interfaz Shape y luego simplemente crear una lista ? Todavía puede probar para clases específicas y lanzar si es necesario ... – kgrad

Respuesta

4

Intente declarar shapeSuper como List<Shape> en su lugar. A continuación, puede hacer

for (Shape shape : shapeSuper) 
23

Para ampliar Paul's answer, declarando shapeSuper como Lista <? super Shape >, estás diciendo que puede aceptar cualquier objeto que sea una súper clase de Shape. Object es una superclase de forma. Esto significa que la superclase común de cada uno de los elementos de la lista es Object.

Es por eso que debe usar el tipo de objeto en el ciclo for. En lo que respecta al compilador, la lista puede contener objetos que no son Formas.

+2

+1 para explicar realmente lo que está mal. Agregue una discusión de "? Super T" vs. "? Extends T" y volveré a votar nuevamente. Oh espera ... –

+0

jeje mmyers. me gusta la explicación también., lástima que no podemos volver a votar por segunda vez –

3

A)

Debido super indica la clase de límite inferior de un elemento genérico. Por lo tanto, List<? super Shape> podría representar List<Shape> o List<Object>.

B)

Debido a que el compilador no sabe cuál es el tipo real de List<? super Shape> es.

Su adición de un objeto con shapeSuper.add(new Object()); pero el compilador sólo sabe que el tipo genérico de List es un tipo súper de Shape, pero no sabe exactamente una bruja que es.

En su ejemplo, List<? super Shape> realmente podría ser List<ShapeBase> forzando al compilador a no permitir la operación shapeSuper.add(new Object());.

Recuerde, Generics are not covariant.

2

(Negación: Nunca he usado "super" como calificativo genérico de comodín, por lo que tomar esto con un grano de sal ...)

Para (a), en realidad no se puede añadir de forma y sus derivados, solo puedes agregar Shape y sus antepasados. Creo que tal vez lo que desea es

List<? extends Shape> shapeSuper = new ArrayList<Shape>(); 

Especificación "se extiende" significa forma y cualquier cosa derivada de la forma. La especificación de "super" significa Forma y cualquier forma de la que se derive.

No estoy seguro (B), a menos que Object no esté implícito. ¿Qué ocurre si declaras explícitamente Shape como public class Shape extends Object?

+0

El problema con (B) se debe a que no puede agregar cosas a un contenedor genérico declarado con un parámetro de comodín. –

+0

En realidad, estaba equivocado. add (new Shape()) funciona bien. El problema es un poco más espinoso que eso. –

17

El "Obtener y Colocar Principio" en la sección (2.4) es una verdadera joya de Java Generics and Collections:

El Obtener y Colocar Principio: utilizar un extiende comodín cuando se consigue solamente valores de una estructura , use el comodín super cuando solo ponga valores en una estructura, y no use un comodín cuando los obtenga y coloque.

alt text http://oreilly.com/catalog/covers/0596527756_cat.gif

Además, se declara un tipo como List<? super Shape> shapeSuper es una especie de mala forma, ya que limita su uso. En general, la única vez que lo uso de comodines está en firmas de método:

public void foo(List<? super Shape> shapeSuper) 
+3

Esto también se conoce como el principio PECS (Effective Java 2nd Ed.). "Producer Extends, Consumer Super". Si el parámetro que está aceptando es un productor, use 'extends'. –

+0

@Julien dices que usas ** super ** cuando ponemos valores. ¿Por qué querremos lanzar súper clases de X en X y luego ponerlas en una lista de X? ¿Cuál es la ventaja de usar super en absoluto? ¿Por qué no simplemente no usar un comodín independientemente de put/get/put + get? – Pacerier

+0

@Pacerier esto podría ser una vieja pregunta, pero creo que leer el libro eficaz de Java (2008) te ayudará a responder tu pregunta. – KyelJmD

0

En referencia a lo anterior, no creo que esto es correcto:

declarando shapeSuper como List<? super Shape> shapeSuper, usted está diciendo que puede aceptar cualquier objeto que es una superclase de forma

eso parece lo intuitivo a primera vista, pero en realidad no creo que es como funciona. No puede insertar cualquier superclase de Shape en shapeSuper. superShape es en realidad una referencia a una lista que puede estar restringida a contener un supertipo particular (pero no especificado) de forma.

Permite imaginar los implementos de formas visibles y extraíbles. Entonces, en este caso, la referencia de superShape puede apuntar realmente a List<Viewable> o List<Drawable> (o de hecho a List<Object>), pero no sabemos cuál. Si es realmente un List<Viewable>, no debería poder insertar una instancia Drawable en él, y el compilador le impedirá hacerlo.

La construcción de límite inferior sigue siendo realmente muy útil para hacer que las clases genéricas sean más flexibles. En el siguiente ejemplo que nos permite pasar al método addShapeToSet un conjunto definido de contener cualquier superclase de forma - y todavía puede insertar una forma en ella:

public void addShapeToSet(Set<? super Shape> set) { 
    set.add(new Shape()); 
} 

public void testAddToSet() { 
    //these all work fine, because Shape implements all of these: 
    addShapeToSet(new HashSet<Viewable>()); 
    addShapeToSet(new HashSet<Drawable>());   
    addShapeToSet(new HashSet<Shape>());   
    addShapeToSet(new HashSet<Object>()); 
} 
25

Para su ejemplo, se puede utilizar una llanura List<Shape> como Dan y Paul dijeron; no necesita usar la sintaxis de signo de interrogación comodín como List<? super Shape> o List<? extends Shape>). Creo que su pregunta subyacente podría ser "¿cuándo usaría una de las declaraciones de estilo de signo de interrogación?" (El Principio de Obtener y poner que cita Julien es una gran respuesta a esta pregunta, pero no creo que tenga mucho sentido a menos que lo vea en el contexto de un ejemplo.) Aquí está mi opinión sobre una versión ampliada del Get y Ponga Principio para cuándo usar comodines.

Use <? extends T> si ...

  • Un método tiene un parámetro genérico clase Foo<T> readSource
  • El método obtiene instancias de T de readSource, y no le importa si el objeto actual recuperado pertenece a una subclase de T.

uso <? super T> ... si

  • un método tiene un parámetro de clase genérica Foo<T> writeDest
  • El método PONE casos de T en writeDest, y no le importa si writeDest también contiene objetos que son subclases de T.

Aquí hay un tutorial de un ejemplo concreto que ilustra la idea detrás de comodines. Imagine que está escribiendo un método processSquare que elimina un cuadrado de una lista, lo procesa y almacena el resultado en una lista de salida. Aquí está una firma de método:

void processSquare(List<Square> iSqua, List<Square> oSqua) 
{ Square s = iSqua.remove(0); s.doSquare(); oSqua.add(s); } 

Ahora se crea una lista de DoubleSquares, que se extienden Square, y tratar de procesarlos:

List<DoubleSquare> dsqares = ... 
List<Square> processed = new ArrayList<Square>; 
processSquare(dsqares, processed); // compiler error! dsquares is not List<Square> 

El compilador genera un error debido a que el tipo de dsquares List<DoubleSquare> doesn 't coincide con el tipo del primer parámetro para processSquare, List<Square>. Quizás un DoubleSquare es-un cuadrado, pero necesita decirle al compilador que un List<DoubleSquare> es-un List<Square> a los fines de su método processSquare. Utilice el comodín <? extends Square> para indicar al compilador que su método puede tomar una lista de cualquier subclase de Square.

void processSquare(List<? extends Square> iSqua, List<Square> oSqua) 

siguiente a mejorar la aplicación para procesar círculos, así como cuadrados. ¿Quieres agregar todas sus formas procesadas en una única lista que incluye tanto los círculos y cuadrados, por lo que ha cambiado el tipo de la lista de procesado de un List<Square> a un List<Shape>:

List<DoubleSquare> dsqares = ... 
List<Circle> circles = ... 
List<Shape> processed = new ArrayList<Square>; 
processSquare(dsqares, processed); // compiler error! processed is not List<Square> 

El compilador falla con un nuevo error. Ahora el tipo de la lista procesada List<Shape> no coincide con el 2do parámetro para procesar la Plaza, List<Square>. Utilice el comodín <? super Square> para indicar al compilador que un parámetro dado puede ser una Lista de cualquier superclase de Square.

void processSquare(List<? extends Square> iSqua, 
          List<? super Square> oSqua) 

Aquí está el código fuente completo para el ejemplo. A veces me resulta más fácil aprender cosas comenzando con un ejemplo de trabajo y luego descomponiéndolo para ver cómo reacciona el compilador.

package wild; 

import java.util.ArrayList; 
import java.util.LinkedList; 
import java.util.List; 

public abstract class Main { 
    // In processing the square, 
    // I'll take for input any type of List that can PRODUCE (read) squares. 
    // I'll take for output any type of List that can ACCEPT (write) squares. 
    static void processSquare(List<? extends Square> iSqua, List<? super Square> oSqua) 
    { Square s = iSqua.remove(0); s.doSquare(); oSqua.add(s); } 

    static void processCircle(List<? extends Circle> iCirc, List<? super Circle> oCirc) 
    { Circle c = iCirc.remove(0); c.doCircle(); oCirc.add(c); } 

    public static void main(String[] args) { 
    // Load some inputs 
    List<Circle> circles = makeList(new Circle()); 
    List<DoubleSquare> dsqares = makeList(new DoubleSquare()); 

    // Collated storage for completed shapes 
    List<Shape> processed = new ArrayList<Shape>(); 

    // Process the shapes 
    processSquare(dsqares, processed); 
    processCircle(circles, processed); 

    // Do post-processing 
    for (Shape s : processed) 
     s.shapeDone(); 
    } 

    static class Shape { void shapeDone() { System.out.println("Done with shape."); } } 
    static class Square extends Shape { void doSquare() { System.out.println("Square!"); } } 
    static class DoubleSquare extends Square {} 
    static class Circle extends Shape { void doCircle() { System.out.println("Circle!"); } } 

    static <T> List<T> makeList(T a) { 
    List<T> list = new LinkedList<T>(); list.add(a); return list; 
    } 

} 
+2

Bien explicado. –

+1

@ThisIsTheDave Entonces, ¿cuál es la diferencia entre ** ** y ** **? – Pacerier

+3

No estoy de acuerdo con la publicación larga. Claro que me tomó un par de minutos leer y digerir, pero estoy agradecido por la explicación detallada y práctica. Ahora comprendo mejor que antes. Si los lectores no están dispuestos a dar unos minutos para comprender algunas reglas bastante intrincadas, entonces tampoco deberían molestarse en tratar de entenderlo. – mdma

Cuestiones relacionadas