Python-like list comprehension in Java

51,421

Solution 1

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.

Solution 2

In Java 8 you can use method references:

List<String> list = ...;
list.replaceAll(String::toUpperCase);

Or, if you want to create a new list instance:

List<String> upper = list.stream().map(String::toUpperCase).collect(Collectors.toList());

Solution 3

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

Solution 4

I'm building this project to write list comprehension in Java, now is a proof of concept in https://github.com/farolfo/list-comprehension-in-java

Examples

// { x | x E {1,2,3,4} ^ x is even }
// gives {2,4}

Predicate<Integer> even = x -> x % 2 == 0;

List<Integer> evens = new ListComprehension<Integer>()
    .suchThat(x -> {
        x.belongsTo(Arrays.asList(1, 2, 3, 4));
        x.is(even);
    });
// evens = {2,4};

And if we want to transform the output expression in some way like

// { x * 2 | x E {1,2,3,4} ^ x is even }
// gives {4,8}

List<Integer> duplicated = new ListComprehension<Integer>()
    .giveMeAll((Integer x) -> x * 2)
    .suchThat(x -> {
        x.belongsTo(Arrays.asList(1, 2, 3, 4));
        x.is(even);
    });
// duplicated = {4,8}
Share:
51,421
euphoria83
Author by

euphoria83

Love computers, technology and Apple. Wanna be entrepreneur.

Updated on June 07, 2020

Comments

  • euphoria83
    euphoria83 almost 4 years

    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.

  • euphoria83
    euphoria83 about 15 years
    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 ?
  • palantus
    palantus about 15 years
    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.
  • palantus
    palantus about 15 years
    Out of curiosity, why is it called Hamcrest? I still can't figure out if it sounds tasty or not.
  • Nat
    Nat about 15 years
    It's an anagram of "matchers".
  • Pyrolistical
    Pyrolistical about 15 years
    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.
  • palantus
    palantus about 15 years
    @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.
  • TM.
    TM. over 14 years
    Great answer, but I'd recommend apache commons CollectionUtils.transform over rolling your own. Still +1 for explaining the concepts though.
  • Adam Hughes
    Adam Hughes about 8 years
    Is there an equivalent to Python's one-liner? Sure, here it's this 50 line block... #justjavathings
  • ArtOfWarfare
    ArtOfWarfare almost 8 years
    Part of the beauty of Python list comprehension is how short it is. Your 6 long lines of Java could be written as just [x * 2 for x in 1, 2, 3, 4 if x % 2 == 0]... 1 line of 41 characters. Not sure how much of your code is just terrible to read because of how damn verbose Java is vs how much is because your library doesn't do things concisely enough.
  • rhbvkleef
    rhbvkleef over 7 years
    It is still better than many other solutions here, I actually like this
  • zpontikas
    zpontikas over 7 years
    The question is 7 years old and Java 8 did not exist then. This should be the accepted answer now ;)
  • Andrea Ligios
    Andrea Ligios over 6 years
    @AdamHughes weren't you able in 2016 to scroll some pixel down to read yurez's 2013 answer, instead of trolling with a hater hashtag under a 2009 answer ?
  • Adam Hughes
    Adam Hughes over 6 years
    Even the one-liner is 6 or so method calls :0