views:

2845

answers:

5

I have the ViewValue class defined as follows:

class ViewValue {

private Long id;
private Integer value;
private String description;
private View view;
private Double defaultFeeRate;

// getters and setters for all properties
}

Somewhere in my code i need to convert a list of ViewValue instances to a list containing values of id fields from corresponding ViewValue.

I do it using foreach loop:

List<Long> toIdsList(List<ViewValue> viewValues) {

   List<Long> ids = new ArrayList<Long>();

   for (ViewValue viewValue : viewValues) {
      ids.add(viewValue.getId());
   }

   return ids;

}

Is there a better approach to this problem?

+4  A: 

EDIT: This answer is based on the idea that you'll need to do similar things for different entities and different properties elsewhere in your code. If you only need to convert the list of ViewValues to a list of Longs by ID, then stick with your original code. If you want a more reusable solution, however, read on...

I would declare an interface for the projection, e.g.

public interface Function<Arg,Result>
{
    public Result apply(Arg arg);
}

Then you can write a single generic conversion method:

public <Source, Result> List<Result> convertAll(List<Source> source,
    Function<Source, Result> projection)
{
    ArrayList<Result> results = new ArrayList<Result>();
    for (Source element : source)
    {
         results.add(projection.apply(element));
    }
    return results;
}

Then you can define simple projections like this:

private static final Function<ViewValue, Long> ID_PROJECTION =
    new Function<ViewValue, Long>()
    {
        public Long apply(ViewValue x)
        {
            return x.getId();
        }
    };

And apply it just like this:

List<Long> ids = convertAll(values, ID_PROJECTION);

(Obviously using K&R bracing and longer lines makes the projection declaration a bit shorter :)

Frankly all of this would be a lot nicer with lambda expressions, but never mind...

Jon Skeet
Very Javaesque solution - but not in a good way. It doesnt exactly reduce the amount or complexity of the code, does it?
Michael Borgwardt
To me too. Overdone.
Adeel Ansari
I'd say it's more LINQ-esque than Java-esque personally. I'd rather create the conversion code and then isolate the projection aspect, but if you'd rather duplicate the conversion logic loads of times, that's fine. It would be a lot nicer with lambda expressions, of course.
Jon Skeet
Does it not something like we do with Comparator in Java?
Adeel Ansari
Not really - that only converts to int from what I remember. However, I approached this with the idea of reuse which may not be required - editing my answer to reflect this.
Jon Skeet
(Note that my "loads of times" comment was assuming this would be required for different properties etc as well, rather than just the one.)
Jon Skeet
You really don't need the ID_PROJECTION field. Creating it each time would give shorter code, in a better order, that probably ran faster.
Tom Hawtin - tackline
(Although I guess it picks up a pointless outer this.)
Tom Hawtin - tackline
It depends on how many places it's going to be used. If it's only going to be used in one place I'd probably put it inline. For more than one, I'd rather just specify it once.
Jon Skeet
+1  A: 

That depends on what you then do with the List<Long>, and the List<ViewValue>

For example you might get sufficient functionality from creating your own List implementation that wraps a List<ViewValue>, implementing iterator() with an iterator implementation that iterates over the ViewValues, returning the id.

Stephen Denne
+1  A: 

You could ude a wrapper:

public class IdList impements List<Long>
{
    private List<ViewValue> underlying;

    pubic IdList(List<ViewValue> underying)
    {
        this.underlying = underying;
    }

    public Long get(int index)
    {
        return underlying.get(index).getId()
    }

    // other List methods
}

though thats even more tedious work, it could improve performance.

You could also implement your and my solution genericaly using reflection, but that woud be very bad for perforance.

Theres no short and easy generic solution in Java, Im afraid. In Groovy, you would simply use collect(), but I believe that involves reflection as well.

Michael Borgwardt
+1  A: 

I've implemented a small functional library for this usecase. One of the methods has this signature:

<T> List<T> mapToProperty(List<?> objectList, String property, Class<T> returnType)

Which takes the string and uses reflection to create a call to the property then it returns a List backed by the objectList where get and iterator implemented using this property call.

The mapToProperty functions is implemented in terms of a general map function that takes a Function as a mapper though, just as another post described. Very usefull.

I suggest you read up on basic functionl programming and in particular take a look at Functors (objects implementing a map function)

Edit: Reflection really doesn't have to be expensive. The JVM has improved a lot in this area. Just make sure to compile the invocation once and reuse it.

Edit2: Sample code

public class MapExample {
    public static interface Function<A,R>
    {
     public R apply(A b);
    }

    public static <A,R> Function<A,R> compilePropertyMapper(Class<A> objectType, String property, Class<R> propertyType)
    {
     try {
      final Method m = objectType.getMethod("get" + property.substring(0,1).toUpperCase() + property.substring(1));

      if(!propertyType.isAssignableFrom(m.getReturnType()))
       throw new IllegalArgumentException(
        "Property "+property+" on class "+objectType.getSimpleName()+" is not a "+propertyType.getSimpleName()
       );

      return new Function<A,R>() 
      {
       @SuppressWarnings("unchecked")
       public R apply(A b)
       {
        try {
         return (R)m.invoke(b);
        } catch (Exception e) {
         throw new RuntimeException(e);
        }
       };
      };

     } catch (Exception e) {
      throw new RuntimeException(e);
     }
    }

    public static <T1,T2> List<T2> map(final List<T1> list, final Function<T1,T2> mapper)
    {
     return new AbstractList<T2>()
     {
      @Override
      public T2 get(int index) {
       return mapper.apply(list.get(index));
      }

      @Override
      public int size() {
       return list.size();
      }
     };
    }

    @SuppressWarnings("unchecked")
    public static <T1,T2> List<T2> mapToProperty(List<T1> list, String property, Class<T2> propertyType)
    {
     if(list == null)
      return null;
     else if(list.isEmpty())
      return Collections.emptyList();

     return map(list,compilePropertyMapper((Class<T1>)list.get(0).getClass(), property, propertyType));
    }
}
John Nilsson
+1  A: 

You could do it in a one-liner using Commons BeanUtils and Collections:
(why write your own code when others have done it for you?)

import org.apache.commons.beanutils.BeanToPropertyValueTransformer;
import org.apache.commons.collections.CollectionUtils;

...

List<Long> ids = (List<Long>) CollectionUtils.collect(viewValues, 
                                       new BeanToPropertyValueTransformer("id"));
Ulf Lindback