2010-04-04 10 views
35

Estoy tratando de entender la palabra clave extends en Java Generics.Genéricos: Lista <? extiende Animal> es igual que la Lista <Animal>?

List<? extends Animal> significa que podemos rellenar cualquier objeto en el cual ListES UNAnimal

entonces no será lo siguiente también significan lo mismo:

List<Animal> 

Puede alguien ayudarme a saber la diferencia entre los dos anteriores? Para mí, extends suena redundante aquí.

Gracias!

Respuesta

58

List<Dog> es un subtipo de List<? extends Animal>, pero no es un subtipo de List<Animal>.

¿Por qué List<Dog> no es un subtipo de List<Animal>? Consideremos el siguiente ejemplo:

void mySub(List<Animal> myList) { 
    myList.add(new Cat()); 
} 

si se dejen pasar una List<Dog> a esta función, se llega a un error en tiempo de ejecución.


EDIT: Ahora, si usamos List<? extends Animal> lugar, sucederá lo siguiente:

void mySub(List<? extends Animal> myList) { 
    myList.add(new Cat());  // compile error here 
    Animal a = myList.get(0); // works fine 
} 

Usted podría pasar una List<Dog> a esta función, pero el compilador da cuenta de que añadir algo a la lista podría meterlo en problemas. Si usa super en lugar de extends (lo que le permite pasar un List<LifeForm>), es al revés.

void mySub(List<? super Animal> myList) { 
    myList.add(new Cat());  // works fine 
    Animal a = myList.get(0); // compile error here, since the list entry could be a Plant 
} 

La teoría detrás de esto es Co- and Contravariance.

+4

+1 Para el enlace de la wikipedia. – helpermethod

+0

En su primer ejemplo, obtendría un error de compilación en lugar de un error en tiempo de ejecución si pasa una lista a una lista

+1

@DonLi: De hecho. Es por eso que uso el [segundo condicional] (https://en.wikipedia.org/wiki/English_conditional_sentences#Second_conditional): * "Si ** se te permitió ** ..." *, es decir, en el escenario hipotético de que el compilador consideró 'List ' como un subtipo de 'List '. – Heinzi

16

No lo es. List<Animal> dice que el valor asignado a esta variable debe ser de "tipo" List<Animal>. Sin embargo, esto no significa que solo debe haber objetos Animal, también puede haber subclases.

List<Number> l = new ArrayList<Number>(); 
l.add(4); // autoboxing to Integer 
l.add(6.7); // autoboxing to Double 

utiliza el constructo List<? extends Number> si estás interesado en una lista que obtuvo Number objetos, pero el propio objeto de lista no tiene que ser de tipo List<Number> pero puede cualquier otra lista de subclases (como List<Integer>).

Esta es utilizar algún momento de los argumentos del método que dicen "Quiero una lista de Numbers, pero no me importa si es sólo List<Number>, que puede ser un List<Double> demasiado". Esto evita algunos lanzamientos extraños si tiene una lista de algunas subclases, pero el método espera una lista de la clase base.

public void doSomethingWith(List<Number> l) { 
    ... 
} 

List<Double> d = new ArrayList<Double>(); 
doSomethingWith(d); // not working 

Esto no está funcionando como se esperaba List<Number>, no un List<Double>.Pero si escribió List<? extends Number> puede pasar objetos List<Double> incluso si no son objetos List<Number>.

public void doSomethingWith(List<? extends Number> l) { 
    ... 
} 

List<Double> d = new ArrayList<Double>(); 
doSomethingWith(d); // works 

Nota: Todo este material está relacionado con la herencia de los objetos en la lista en sí. Aún puede agregar objetos Double y Integer en una lista de List<Number>, con o sin ? extends cosas.

+0

hmmm ... obteniéndolo. Buena explicación – peakit

35

Veo que ya ha aceptado una respuesta, pero me gustaría añadir mi opinión, ya que creo que puedo ser de ayuda aquí.

La diferencia entre List<Animal> y List<? extends Animal> es la siguiente.


Con List<Animal>, ya sabes lo que tienes es definitivamente una lista de animales. No es necesario que todos ellos sean exactamente 'Animales'; también podrían ser tipos derivados. Por ejemplo, si tiene una Lista de animales, tiene sentido que una pareja pueda ser Cabra, y algunos de ellos Gatos, etc. ¿Verdad?

Por ejemplo, esto es totalmente válida:

List<Animal> aL= new List<Animal>(); 
aL.add(new Goat()); 
aL.add(new Cat()); 
Animal a = aL.peek(); 
a.walk(); // assuming walk is a method within Animal 

Por supuesto, la siguiente sería no ser válida:

aL.peek().meow(); // we can't do this, as it's not guaranteed that aL.peek() will be a Cat 

Con List<? extends Animal>, estás haciendo una declaración acerca el tipo de la lista que está tratando.

Por ejemplo:

List<? extends Animal> L; 

Esto es en realidad no una declaración de tipo de objeto L puede mantener. Es una afirmación sobre los tipos de listas a las que L puede hacer referencia.

Por ejemplo, podríamos hacer esto:

L = aL; // remember aL is a List of Animals 

Pero ahora todo el compilador sabe sobre L es que se trata de un Lista de [ya sea animal o un subtipo de Animal] s

Así que ahora lo siguiente no es válido:

L.add(new Animal()); // throws a compiletime error 

Debido a que por lo que sabemos, L podría ser hacer referencia a una lista de las cabras - a wh No podemos agregar un Animal.

He aquí por qué:

List<Goat> gL = new List<Goat>(); // fine 
gL.add(new Goat()); // fine 
gL.add(new Animal()); // compiletime error 

En lo anterior, estamos tratando de echar un animal como una cabra. Eso no funciona, porque ¿qué pasa si después de hacer eso tratamos de hacer que Animal haga un "cabezazo", como lo haría una cabra? No necesariamente sabemos que el Animal puede hacer eso.

+4

+1 para "Esto realmente no es una declaración del tipo de objeto que L puede contener. Es una declaración sobre a qué tipos de listas puede hacer referencia L". – Heinzi

+0

y por la misma razón no puede hacer 'L.add (new Goat())' coz L podría estar haciendo referencia a una lista de Perros a los que no se puede agregar Cabra :) – Tarun

+0

contento de que me desplacé hacia abajo ... gracias por la explicación.. –

Cuestiones relacionadas