Python-like list comprehension in Java
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}
euphoria83
Love computers, technology and Apple. Wanna be entrepreneur.
Updated on June 07, 2020Comments
-
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 about 15 yearsBut 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 about 15 yearsNot 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 about 15 yearsOut of curiosity, why is it called Hamcrest? I still can't figure out if it sounds tasty or not.
-
Nat about 15 yearsIt's an anagram of "matchers".
-
Pyrolistical about 15 yearsdoToList 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 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. over 14 yearsGreat answer, but I'd recommend apache commons
CollectionUtils.transform
over rolling your own. Still +1 for explaining the concepts though. -
Adam Hughes about 8 yearsIs there an equivalent to Python's one-liner? Sure, here it's this 50 line block... #justjavathings
-
ArtOfWarfare almost 8 yearsPart 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 over 7 yearsIt is still better than many other solutions here, I actually like this
-
zpontikas over 7 yearsThe question is 7 years old and Java 8 did not exist then. This should be the accepted answer now ;)
-
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 over 6 yearsEven the one-liner is 6 or so method calls :0