views:

1132

answers:

3

Since Java doesn't allow passing methods as parameters, what trick do you use to implement Python like list comprehension in Java ?

I have a list (ArrayList) of Strings. I need to transform each element by using a function so that I get another list. I have several functions which take a String as input and return another String as output. How do I make a generic method which can be given the list and the function as parameters so that I can get a list back with each element processed. It is not possible in the literal sense, but what trick should I use ?

The other option is to write a new function for each smaller String-processing function which simply loops over the entire list, which is kinda not so cool.

+6  A: 

The Google Collections library has lots of classes for working with collections and iterators at a much higher level than plain Java supports, and in a functional manner (filter, map, fold, etc.). It defines Function and Predicate interfaces and methods that use them to process collections so that you don't have to. It also has convenience functions that make dealing with Java generics less arduous.

I also use Hamcrest** for filtering collections.

The two libraries are easy to combine with adapter classes.


** Declaration of interest: I co-wrote Hamcrest

Nat
Out of curiosity, why is it called Hamcrest? I still can't figure out if it sounds tasty or not.
Michael Myers
It's an anagram of "matchers".
Nat
+12  A: 

Basically, you create a Function interface:

public interface Func<In, Out> {
    public Out apply(In in);
}

and then pass in an anonymous subclass to your method.

Your method could either apply the function to each element in-place:

public static <T> void applyToListInPlace(List<T> list, Func<T, T> f) {
    ListIterator<T> itr = list.listIterator();
    while (itr.hasNext()) {
        T output = f.apply(itr.next());
        itr.set(output);
    }
}
// ...
List<String> myList = ...;
applyToListInPlace(myList, new Func<String, String>() {
    public String apply(String in) {
        return in.toLowerCase();
    }
});

or create a new List (basically creating a mapping from the input list to the output list):

public static <In, Out> List<Out> map(List<In> in, Func<In, Out> f) {
    List<Out> out = new ArrayList<Out>(in.size());
    for (In inObj : in) {
        out.add(f.apply(inObj));
    }
    return out;
}
// ...
List<String> myList = ...;
List<String> lowerCased = map(myList, new Func<String, String>() {
    public String apply(String in) {
        return in.toLowerCase();
    }
});

Which one is preferable depends on your use case. If your list is extremely large, the in-place solution may be the only viable one; if you wish to apply many different functions to the same original list to make many derivative lists, you will want the map version.

Michael Myers
But then you are asking me to put every small function in a different class since they have to have a standard name ('apply' in your case). Right ?
euphoria83
Not necessarily; your anonymous class can simply call the small function inside apply(). This is as close as Java gets to function pointers without venturing into the perils of reflection.
Michael Myers
doToList is reinventing the wheel. What you have done here is a poor design of what is usually called map. The usual interface is public static <T, U> List<U> map(List<T>, Func<T, U> f); What it does is produce an another list instead of modifying the one in place. If you need to modify the original list without destroying the reference, then just do a .clear() followed by an addAll(). Don't combine all that in one method.
Pyrolistical
@Pyrolistical: Yes, I do realize that. This example is specifically tailored to the question, which did not specify whether a new list was required or not. I guess it would be clearer to rename it to applyToListInPlace or something.
Michael Myers
Yes, I do need a new list.
euphoria83
Great answer, but I'd recommend apache commons `CollectionUtils.transform` over rolling your own. Still +1 for explaining the concepts though.
TM
+3  A: 

Apache Commons CollectionsUtil.transform(Collection, Transformer) is another option.

Unfortunately, it's not generic. In this case all that means is some extra casting, but it could be a problem in other cases.
Michael Myers