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.

3
  • I think you're stuck looping, or if you're adventurous you can take a look at quaere. Take a look at What is the Java equivalent for LINQ? for some related information. Commented Jun 24, 2010 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. Commented Jun 24, 2010 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! Commented Jun 25, 2010 at 16:25

8 Answers 8

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:

Sign up to request clarification or add additional context in comments.

5 Comments

Cool, good to see a link to a library that does essentially what I was suggesting in my post.
@Mark: the big difference is that Guava's implementation applies the function on-demand. Sort of "lazy transform", where as yours is eager.
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: I looked around and found Collections2.transform. But yes, there's Iterables.addAll from Iterable to Collection.
@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.
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;
}

1 Comment

"Perl's" map function? Hehe! Don't you mean Java's equivalent of Perl's equivalent of Lisp's map function.
4

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

Comments

3

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

2 Comments

Using LambdaJ you just need to write the converter Customer->String
Or better yet ;-) guava's Lists.transform guava-libraries.googlecode.com/svn/trunk/javadoc/com/google/…, com.google.common.base.Function)
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();
  }
});

1 Comment

Interesting. Do you know if it works with jdk 1.5 or is it Java 6 up only?
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

Comments

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.

1 Comment

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!
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.

1 Comment

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.