views:

167

answers:

3

I have a java application which uses JPA.

Say I have an entity called Product with name and price attributes (all entities have an id attribute).

Naturally I can get a List<Product> fairly easily (from a query or another entity), but often I want a List<String> (list of product names) or List<Long> (list of product prices or list of product ids).

A lot of the time it's just as easy to pass the whole Product around, but there are two cases where I don't want to do this:

  • I'm passing the list to a class which shouldn't have a dependency on the Product class.
  • It's significantly easier/faster to get a list of ids than full product objects, (but in some cases I already have them).

The naive way to do this would go something like:

List<Long> productIds = new ArrayList<Long>();
for(Product product: products) {
  productIds.add(product.getId());
}

But I don't like this because it's messy and inefficient. In python I would do something like:

[ p.id for p in products ]

The "best" I can come up with in Java is:

public class ProductIdList extends AbstractList<Long> {
  private List<Product> data;

  public ProductIdList(List<Product> data) {
    this.data = data;
  }

  public Long get(int i) {
    return data.get(i).getId();
  }

  public int size() {
    return data.size();
  }

  public Long remove(int i) {
    return data.remove(i).getId();
  }

  /* For better performance */
  public void clear() {
    data.clear();
  }

  /* Other operations unsupported */
}

Pros:

  • This approach doesn't need to copy the data
  • It is a true "view" on the data - changes to the underlying list are reflected.

Cons:

  • Seems like a lot of code
  • Need a class like this for each attribute I want to access like this

So is this a good idea or not? Should I just be creating secondary lists most of the time? Is there a third option I haven't considered?

A: 

You could use one Class that has one method for each attribute. That would reduce one CONS at least.

This is your example tuned with my proposal.

public class ProductList {
  private List<Product> data;

  public ProductIdList(List<Product> data) {
    this.data = data;
  }

  public Long getId(int i) {
    return data.get(i).getId();
  }

  public List<String> getProductNames(int i) {
    return data.get(i).getProductNames();
  }

  public Product getProduct(int i) {
    return data.get(i);
  }

  public int size() {
    return data.size();
  }

  public Long remove(int i) {
    return data.remove(i).getId();
  }

  /* For better performance */
  public void clear() {
    data.clear();
  }
}
StampedeXV
This doesn't implement List, so it can't be used where a List<Long> is expected.
Draemon
+1  A: 

You could minimize the size of those per-attribute wrapper classes by having them inherit from an abstract class that has a getAttribute method. Something like this:

public abstract class AbstractProductList<T> extends AbstractList<T> {
  protected List<Product> data;

  public AbstractProductList(List<Product> data) {
    this.data = data;
  }

  protected abstract T getAttribute(Product product);

  public T get(int i) {
    return getAttribute(data.get(i));
  }

  public int size() {
    return data.size();
  }

  public T remove(int i) {
    return getAttribute(data.remove(i));
  }

  /* For better performance */
  public void clear() {
    data.clear();
  }

  /* Other operations unsupported */
}

public class ProductIdList extends AbstractProductList<Long> {
  public ProductIdList(List<Product> data) {
    super(data);
  }

  protected Long getAttribute(Product product) {
    return product.getId();
  }
}
Jeremy Stein
I like this. I think the Product class could be made generic too.
Draemon
+2  A: 

Just to throw in another possibility, you could use Apache Commons' Transformer and collect to create a new collection or transform to modify the existing one:

  Collection<Long> productIds = CollectionUtils.collect(products, new Transformer() {
      public Long transform(Object o) {
          return ((Product) o).id;
      }
  });
JRL
This is probably the most correct way to implement exactly what I want. It's unfortunate that Commons hasn't caught up with generics though, so that and the additional library mean I can't really justify it. Still, +1
Draemon