List Comprehensions for Java

March 2, 2008

I’ve added Python-style list comprehensions to javac. It works on Iterables. You can do things like:

Iterable<Integer> list = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
list = [x*x for int x : list if x >= 5];

You’ll get an Iterable that can generate the sequence 25, 36, 49, 64, and 81. I use comprehensions all the time in Python to process text files. In Java this would look like:

Iterable<String> lines = new ScannerIterable(new File("input.txt"));

// Remove end-of-line character.
lines = [line.substring(0, line.length() - 1) for String line : lines];

// Strip leading and trailing whitespace.
lines = [line.trim() for String line : lines];

// Remove blank lines.
lines = [line for String line : lines if !line.isEmpty()];

// Remove comments.
lines = [line for String line : lines if !line.startsWith("#")];

// Split up at spaces.
Iterable<String[]> tokenizedLines = [line.split(" ") for String line : lines];

(ScannerIterable is a helper class that generates a new Scanner each time iterator() is called, setting the delimiter to end-of-line.)

I find this way of coding very readable and maintainable, plus it doesn’t load the whole file into memory. The motivation for this came from all the recent discussions about closures. It occurred to me that although I never used map() and filter() in Python, once comprehensions were introduced in Python 2.0 I found myself using them pervasively. The map+lambda (or filter+lambda) syntax never clicked with me, but the comprehension syntax came naturally. Comprehensions don’t replace closures, of course, but they may provide a more natural expression when mapping and filtering.

A binary release (broken link—contact me if you want it) is available for testing, with examples. The source is also available; it’s the listcomprehensions branch of the kijaro project. Look for LISTCOMP comments for the changes, or diff with the trunk. You can also read a more thorough write-up of the motivation and spec, with more examples.