69

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.

S.Lott
  • 384,516
  • 81
  • 508
  • 779
euphoria83
  • 14,768
  • 17
  • 63
  • 73
  • 2
    as an fyi, you could use Jython or Scala to get list comprehensions on the JVM – geowa4 Sep 15 '09 at 21:13
  • 1
    SMH while reading all of the answers on this. In Python, you can easily write a list comprehension in one line of 40-60 characters. All of the proposed solutions here are multiple lines, and most of them longer than the single line it would take in Python. – ArtOfWarfare Jul 19 '16 at 20:00
  • @ArtOfWarfare I think yurez's Java 8 replaceAll solution is close enough to Pythonic. – Noumenon Mar 02 '18 at 14:20
  • 2
    @Noumenon - Yeah, that does look pretty great. I wonder if I just missed it or something when I made my comment. His example would be just 8 characters shorter in Python, the same number of lines, and arguably easier to read. Plus 6 of those characters come from Java's `toUpperCase` vs Python's `upper`, which isn't really relevant to the question. – ArtOfWarfare Mar 02 '18 at 16:02

7 Answers7

83

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());
yurez
  • 2,826
  • 1
  • 28
  • 22
  • 25
    The question is 7 years old and Java 8 did not exist then. This should be the accepted answer now ;) – zpontikas Feb 24 '17 at 13:18
38

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
  • 188,989
  • 46
  • 291
  • 292
  • 1
    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 May 22 '09 at 18:06
  • 1
    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 May 22 '09 at 18:08
  • 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 List map(List, Func 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 May 22 '09 at 18:30
  • @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 May 22 '09 at 18:46
  • Great answer, but I'd recommend apache commons `CollectionUtils.transform` over rolling your own. Still +1 for explaining the concepts though. – TM. Dec 09 '09 at 07:09
  • Is there an equivalent to Python's one-liner? Sure, here it's this 50 line block... #justjavathings – Adam Hughes Mar 14 '16 at 13:06
  • @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 ? – Andrea Ligios Sep 22 '17 at 12:45
  • Even the one-liner is 6 or so method calls :0 – Adam Hughes Sep 25 '17 at 11:37
17

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
  • 9,820
  • 3
  • 31
  • 33
5

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

Michael Myers
  • 188,989
  • 46
  • 291
  • 292
  • 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 May 22 '09 at 20:27
2

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}
farolfo
  • 388
  • 1
  • 4
  • 13
  • 3
    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. – ArtOfWarfare Jul 19 '16 at 20:04
  • It is still better than many other solutions here, I actually like this – rhbvkleef Sep 27 '16 at 08:24
0

You can use lambdas for the function, like so:

class Comprehension<T> {
    /**
    *in: List int
    *func: Function to do to each entry
    */
    public List<T> comp(List<T> in, Function<T, T> func) {
        List<T> out = new ArrayList<T>();
        for(T o: in) {
            out.add(func.apply(o));
        }
        return out;
    }
}

the usage:

List<String> stuff = new ArrayList<String>();
stuff.add("a");
stuff.add("b");
stuff.add("c");
stuff.add("d");
stuff.add("cheese");
List<String> newStuff = new Comprehension<String>().comp(stuff, (a) -> { //The <String> tells the comprehension to return an ArrayList<String>
    a.equals("a")? "1": 
            (a.equals("b")? "2": 
                (a.equals("c")? "3":
                    (a.equals("d")? "4": a
    )))
});

will return:

["1", "2", "3", "4", "cheese"]
0
import java.util.Arrays;

class Soft{
    public static void main(String[] args){
        int[] nums=range(9, 12);
        System.out.println(Arrays.toString(nums));
    }
    static int[] range(int low, int high){
        int[] a=new int[high-low];
        for(int i=0,j=low;i<high-low;i++,j++){
            a[i]=j;
        }
        return a;

    }
}

Hope, that I help you :)