2010-09-21 26 views
5

digamos que tenemos un programa que contiene dichas clases:Java problema colecciones covarianza

public interface AbstractItem { 
} 
public SharpItem implements AbstractItem { 
} 
public BluntItem implements AbstractItem { 
} 

public interface AbstractToolbox { 
    //well the problem starts here... 
    public List<AbstractItem> getItems(); 
} 
public ExpensiveToolbox implements AbstractToolbox { 
    private List<SharpItem> items = new ArrayList()<SharpItems>; 
    public List<SharpItem> getItems() { return this.items; } 
} 
public CheapTooblox implements AbstractToolbox { 
    private List<BluntItem> items = new ArrayList()<BluntItem>; 
    public List<BluntItem> getItems() { return this.items; } 
} 

fácil, ¿verdad? Bueno digamos que ahora queremos hacer un método como este (de alguna clase al azar):

public void doImportantStuff(AbstractToolbox toolbox) { 
//important stuff! 
//this obviously won't work 
    List<AbstractToolbox> items = toolbox.getItems(); 
//do some stuffwith all items 
} 

Ahora el problema es que en Java colecciones con los genéricos no son covariantes (la esperanza de que es el término que estoy buscando) y no puedo asignar un ArrayList<ExpensiveToolbox> a un List<AbstractToolbox>. La única solución que puedo ver aquí es duplicar el código y hacer una versión para cada tipo, pero eso obviamente apestaría (¿y si tuviéramos más clases implementando AbstractToolbox con diferentes listas?). Oh, obviamente, la segunda solución sería soltar los genéricos y hacer una lista normal, pero ¿es una buena práctica?

¿Existen patrones/prácticas de diseño para abordar estos problemas?

@Edit: ok, entonces podría no ser lo suficientemente preciso. Quiero que todas las clases que extienden AbstractToolbox tengan una Lista de ciertas clases que extiendan AbstractItem y luego quiero un método que tome un AbstractToolbox como parámetro y haga algo en los elementos de su lista (usando las clases que se definirían en AbstractItem para que todos los elementos de todas las listas posibles realmente los tengan).

+0

Para aclarar, no le gustaría que su doImportantStuff que tiene: List <> AbstractItem artículos = toolbox.getIems(); – MikeTheReader

+0

¿Qué quieres decir con "Fácil, ¿verdad?" ¡Tu primera sección no se compila y no debería! –

+1

Bueno, pensé que sería fácil para SO. Quiero decir, obviamente, * I * estoy teniendo problemas con eso;) –

Respuesta

16

Probablemente tenga que echar un vistazo al uso de tipos de comodines genéricos. Aquí hay un enlace rápido: What is PECS (Producer Extends Consumer Super)?

respuesta rápida: cambia el tipo de List<? extends AbstractItem>

Por qué no puede simplemente asignar este?

Imagine el código aquí ...

List<AbstractItem> foo = new ArrayList<SharpItem>(); 
foo.add(new BluntItem()); 

La tipificación estática dice que esto debería funcionar ... pero no se puede hacer eso! Violaría el tipo de ArrayList. Es por eso que esto no está permitido. Si lo cambia a

List<? extends AbstractItem> foo = new ArrayList<SharpItem>(); 

puede realizar la tarea, pero nunca agregue nada a la lista. Sin embargo, puede recuperar elementos de la lista como AbstractItems.

¿Simplemente está utilizando List (bare type) una buena solución?

No, definitivamente no :-P

+3

O, como alternativa, escriba AbstractToolbox con un parámetro de tipo y utilícelo en su lugar. –

+0

Pero cuando aceptas un AbstractToolbox debes tratar con el parámetro T o comodín * que *. Si solo tiene que tratar con AbstractItems, tiene la buena propiedad de no tener que agregar el parámetro de tipo a otras partes del código. –

+0

¡Ámalo, gracias! –

4

Aquí hay un par de ideas adicionales. Deja todo lo mismo, pero el uso de este:

interface AbstractToolbox { 
    public List<? extends AbstractItem> getItems(); 
} 

Esto básicamente dice que los artículos de la clase abstracta son un tipo desconocido, pero subclases puede hacer que sea concreto. Esto requeriría que llamaras al getItems() en una referencia de tipo ExpensiveToolbox o CheapToolbox para poder recuperar una lista que te permite agregar elementos, etc.

ExpensiveToolbox toolbox = new ExpensiveToolbox(); 
AbstractToolbox absTB = toolbox; 

List<? extends AbstractItem> items1 = absTB.getItems(); //fine 
List<SharpItem> items2 = absTB.getItems(); //compile error 
List<SharpItem> items3= toolbox.getItems(); //fine 

Como alternativa, puede simplemente escribir AbstractToolbox:

public interface AbstractToolbox<T extends AbstractItem> { 
    public List<T> getItems(); 
} 
public ExpensiveToolbox implements AbstractToolbox<SharpItem> { 
    public List<SharpItem> getItems() { //... 
} 
+0

+1 para escribir AbstractToolbox, creo que esta es la solución obvia. –

0
public interface AbstractItem 
{ 
} 
public class SharpItem implements AbstractItem 
{ 
} 
public class BluntItem implements AbstractItem 
{ 
} 

public interface AbstractToolbox<T extends AbstractItem> 
{ 
    public List<T> getItems(); 
} 
public class ExpensiveToolbox implements AbstractToolbox<SharpItem> 
{ 
    private List<SharpItem> items = new ArrayList<SharpItem>(); 
    public List<SharpItem> getItems() { return this.items; } 
} 
public class CheapToolbox implements AbstractToolbox<BluntItem> 
{ 
    private List<BluntItem> items = new ArrayList<BluntItem>(); 
    public List<BluntItem> getItems() { return this.items; } 
} 


public void doImportantStuff(AbstractToolbox<?> toolbox) 
{ 
    List<? extends AbstractItem> items = toolbox.getItems(); 

    for(AbstractItem item : items) 
     ... ; 

}