6

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.

CoolBeans
  • 20,654
  • 10
  • 86
  • 101
  • I think you're stuck looping, or if you're adventurous you can take a look at [quaere](http://xircles.codehaus.org/projects/quaere). Take a look at [What is the Java equivalent for LINQ?](http://stackoverflow.com/questions/1217228/what-is-the-java-equivalent-for-linq) for some related information. – Roman Jun 24 '10 at 17:42
  • I think you're not looking for a more efficient way, but perhaps for a more efficient way. The default for-loop is efficient enough, already. – OscarRyz Jun 24 '10 at 20:48
  • Well not necessarily efficacy wise, but I was trying to get an idea of what others do for routine tasks like this one. I learned about Guava and Lambdaj are some of the popular alternatives. So that's good! – CoolBeans Jun 25 '10 at 16:25

8 Answers8

6

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:

Community
  • 1
  • 1
polygenelubricants
  • 376,812
  • 128
  • 561
  • 623
  • Cool, good to see a link to a library that does essentially what I was suggesting in my post. – Mark Peters Jun 24 '10 at 17:49
  • @Mark: the big difference is that Guava's implementation applies the function on-demand. Sort of "lazy transform", where as yours is eager. – polygenelubricants Jun 24 '10 at 17:51
  • 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 Jun 24 '10 at 17:54
  • @Mark: I looked around and found `Collections2.transform`. But yes, there's `Iterables.addAll` from `Iterable` to `Collection`. – polygenelubricants Jun 24 '10 at 18:00
  • 1
    @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 Jun 24 '10 at 18:02
5

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
  • 80,126
  • 17
  • 159
  • 190
  • "Perl's" map function? Hehe! Don't you mean Java's equivalent of Perl's equivalent of Lisp's map function. – dsmith Jun 24 '10 at 18:27
4

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

JoshP
  • 355
  • 1
  • 9
3

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

froadie
  • 79,995
  • 75
  • 166
  • 235
  • 1
    Using LambdaJ you just need to write the converter Customer->String – David Rabinowitz Jun 24 '10 at 17:56
  • 2
    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 Jun 24 '10 at 18:02
2

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
  • 29,904
  • 14
  • 93
  • 125
2

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
  • 169,610
  • 28
  • 168
  • 175
2

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.NAME for instance.

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

for (String name : transform(customers, Customer.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
  • 108,630
  • 30
  • 201
  • 202
  • 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 Jun 24 '10 at 19:40
1

.... 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
  • 196,001
  • 113
  • 385
  • 569