tags:

views:

157

answers:

8

Let's say I have a bean like below.

class Customer{
  private String code;
  private String name;
  private Integer value;
  //getters setters omitted for brevity
}

Then from a method I get a List<Customer> back. Now let's say I want to get a list of all member "name" from the List. Obviously I can traverse and build a List<String> of element "name" myself.

However, I was wondering if there is a short cut or more effiecient way to this technique that anyone knows . For instance, if I want to get a list of all keys in a Map object I get do map.keySet(). Something along that line is what I am trying to find out.

+3  A: 

I think this is something that you would have to code yourself, in a loop.

froadie
Using LambdaJ you just need to write the converter Customer->String
David Rabinowitz
Or better yet ;-) guava's Lists.transform http://guava-libraries.googlecode.com/svn/trunk/javadoc/com/google/common/collect/Lists.html#transform(java.util.List, com.google.common.base.Function)
Dimitris Andreou
+4  A: 

Looks like you're looking for the Java equivalent of Perl's map function. This kind of thing might be added to the collections library once (if) Java receives closures. Until then, I think this is the best you can do:

List<String> list = new ArrayList<String>(customers.size());
for ( Customer c : customers ) {
    list.add(c.getName());
}

You could also write a map function that uses a simple interface to provide the mapping function. Something like this:

public interface Transform<I, O> {
    O transform(I in);
}
public <I, O> List<O> map(Collection<I> coll, Transform<? super I, ? extends O> xfrm) {
    List<O> list = new ArrayList<O>(coll.size());
    for ( I in : coll ) {
        list.add(xfrm.transform(in));
    }
    return list;
}
Mark Peters
"Perl's" map function? Hehe! Don't you mean Java's equivalent of Perl's equivalent of Lisp's map function.
dsmith
+2  A: 

could use something like this: http://code.google.com/p/lambdaj/

JoshP
+5  A: 

Guava has Lists.transform that can transform a List<F> to a List<T>, using a provided Function<F,T> (or rather, Function<? super F,? extends T>).

From the documentation:

public static <F,T>
   List<T> transform(
               List<F> fromList,
               Function<? super F,? extends T> function
           )

Returns a list that applies function to each element of fromList. The returned list is a transformed view of fromList; changes to fromList will be reflected in the returned list and vice versa.

The function is applied lazily, invoked when needed.

Similar live-view transforms are also provided as follows:

polygenelubricants
Cool, good to see a link to a library that does essentially what I was suggesting in my post.
Mark Peters
@Mark: the big difference is that Guava's implementation applies the function on-demand. Sort of "lazy transform", where as yours is eager.
polygenelubricants
@polygenelubricants: Ah, I was curious as to why they were specifically using iterable. That explains the reasoning. I like it even more now (assuming there's a factory method in Guava to create a collection from an iterable?)
Mark Peters
@Mark: I looked around and found `Collections2.transform`. But yes, there's `Iterables.addAll` from `Iterable` to `Collection`.
polygenelubricants
@Mark Peters Yeah, Guava can create any type of `Collection` from an `Iterable` with a factory method from a class like `Lists` or the `copyOf(Iterable)` factory method on all the immutable collection classes.
ColinD
+2  A: 

You can use LambdaJ's Converter interface and have the following line:

List<String> customerNames = convert(customerList, new Converter<Customer,String>() {
  public String convert(Customer customer) {
    return customer.getName();
  }
});
David Rabinowitz
Interesting. Do you know if it works with jdk 1.5 or is it Java 6 up only?
CoolBeans
AFAIK it needs Java 5 only
David Rabinowitz
+2  A: 

You need to use a loop, but the function you're looking for is called map in functional languages. It's possible to implement map in Java, although it tends to be fairly inelegant; here's the version I implemented ages ago in my "stuff Java should have but for some reason doesn't" library:

public interface MapFunction<T, U> {
    public U map(T source);
}

public static <T, U> U[] map(T[] objects, MapFunction<T, U> f) {
    if(objects.length == 0) {throw new IllegalArgumentException("Can't map onto an empty array");}
    @SuppressWarnings("unchecked") U[] rtn = (U[])Array.newInstance(f.map(objects[0]).getClass(), objects.length);
    for(int i = 0; i < objects.length; i++)
        rtn[i] = f.map(objects[i]);
    return rtn;
}

Using that, you could do:

List<Customer> list = yourFunction();
List<String> names = Arrays.asList(map(list.toArray(new Customer[0]), new MapFunction<Customer, String>() {
    public String map(Customer c) {
        return c.getName();
    }
}));

You could naturally change map to take collections instead of arrays, which would eliminate the need for Arrays.asList and List.toArray

Michael Mrozek
+1  A: 

Using Guava, you can use a Function along with Iterables.transform, Collections2.transform or Lists.transform to create an Iterable, Collection or List respectively.

Iterable<String> names = Iterables.transform(customers, 
    new Function<Customer, String>() {
      public String apply(Customer from) {
        return from.getName();
      }
    });

The returned Iterable is lazy and applies the function to the underlying list as you iterate through it. For a List<String> containing the names, you could use:

List<String> names = Lists.transform(...);

or

ImmutableList<String> names = ImmutableList.copyOf(Iterables.transform(...));

Of course, writing out the anonymous inner class Function implementation each time you want to do this is ugly and verbose, so you may want to make the Function a constant available from the Customer class, called Customer.GET_NAME for instance.

Then the transformation looks much nicer (with static imports especially):

for (String name : transform(customers, Customer.GET_NAME)) { ... }

I also wrote about using interfaces for particular properties of objects (such as name here) to help with consolidating such functions on my blog here.

ColinD
Okay both you and polygene sort of gave similar answers. If I could accept two answers as correct then I would have accepted yours too. Thanks!
CoolBeans
+1  A: 

.... is there a short cut or more efficient way

So, are you looking for a more efficient way to do this:

 List<String> names = new ArrayList<String>();
 for( Customer customer : yourCustomerList ) {
     names.add( customer.getName() );
 }

?!!!!

Or just a different way?

All the previous answer are not really more efficient in terms of runtime nor coding. They are however more flexible without a doubt.

Another alternative would be to include Scala Groovy in your Java code and use this:

list.map( _.name )

list.collect { it.name }

If compiled, Groovy classes may be used from Java, or you can plug in them as an script.

Here's a sample for the given Customer class using Groovy as script.

    List<Customer> customers = Arrays.asList( new Customer[]{
       new Customer("A","123",1),
       new Customer("B","456",2),
       new Customer("C","789",3),
       new Customer("D","012",4)
    });

    setVariable(customers, "list");
    evaluate("names = list.collect { it.name } ");
    List<String> names = (List<String>) getVariable("names");
    System.out.println("names = " + names);

Output:

names = [A, B, C, D]

note: I extracted method for readability, but you can see them below

But, again that's just different, not really more efficient than the regular for loop.

Here's the complete source code. To run it you just need Java1.6 and Groovy in the classpath.

OscarRyz
Thanks, I have heard about Groovy but have not messed with it yet. This looks neat.
CoolBeans