Introduction
Closures represent probably the most important feature that is missing in the toolbox of each Java programmer. Some programmers don't feel (or don't understand) the need for closures, while some others do and probably for this reason are evaluating migration towards a functional enabled programming language. The majority of us just learned to partially workaround this lack by using the verbose and poorly readable (anonymous) inner classes.
Actually the opportunity to provide Java with this feature has been vastly debated and for a certain amount of time, it seemed they were going to introduce it in Java 7 through the (in my opinion, awful) BGGA specification. In the end, they decided to give up leaving the Java developers still orphans of closures.
lambdaj's Closure
lambdaj tries to partially fill this gap by introducing in its release 2.0 a new feature that allows to define, in its traditional DSL style, first-class functions with free variables like in the following example:
Closure println = closure(); { of(System.out).println(var(String.class)); }
I believe it is straightforward to understand what the
println
closure does. In particular, the
var()
method binds a free variable of type
String
to the closure. Moreover, note that the curly brackets around the statement that define the behavior of the closure are only syntactic sugar and they could be safely removed even if you find the code more readable by keeping them. You can then invoke this closure by "closing" its free variable once:
println.apply("one");
or more times:
println.each("one", "two", "three");
As you can expect, this last statement will cause the String
s "one
", "two
" and "three
" to be printed on 3 different lines of the Java standard output. It is possible to create both untyped (as in the former example) and strongly typed closures. Supposing your classes have a method that sums 2 int
s:
public int sum(int a, int b) {
return a + b;
}
It is possible to instance a closure that invokes this method as it follows:
Closure2<Integer,Integer> adder = closure(Integer.class, Integer.class); {
of(this).sum(var(Integer.class), var(Integer.class));
}
While you were allowed to call the first closure with any type and number of variables (it will eventually throw an Exception if invoked in a wrong way), you can invoke this second one only by passing 2 int
s to it as expected:
int result = (Integer)adder.apply(2, 3);
Curry
Another feature typically available on closures is the so called curry that allows to create another closure by fixing the value of some free variables. For example, you can have a closure of one free argument that adds 10 to any number by doing a curry of the second variable of the former closure as it follows:
Closure1<Integer> adderOf10 = adder.curry2(10);
In this way, by invoking this last closure with the value 3...
int result = (Integer)adderOf10.apply(3);
... you will obtain 13 as expected. In the end, note that you could achieve exactly the same result by directly creating a closure with only one free parameter and the second one already fixed to 10 as in this last statement:
Closure1<Integer> adderOf10 = closure(Integer.class, Integer.class); {
of(this).sum(var(Integer.class), 10);
}
Why Are Closures Useful?
If you still don't see how closures can be useful for you, let me make a slightly more complex example that could show why I think that they are the most powerful tool in order to generalize your code and thus avoid duplications. For this purpose, let's write a method that reads a file from the classpath and then prints its content line by line on the Java standard output:
public void printFile(String fileName) {
BufferedReader reader = null;
try {
InputStream stream = getClass().getClassLoader().getResourceAsStream(fileName);
reader = new BufferedReader(new InputStreamReader(stream));
for (String line = reader.readLine();
line != null; line = reader.readLine()) {
System.out.println(line);
}
} catch (IOException ioe) {
throw new RuntimeException("Error while reading file " + fileName, ioe);
} finally {
try {
if (reader != null) reader.close();
} catch (IOException ioe) {
throw new RuntimeException("Error while closing file reader", ioe);
}
}
}
There are lots of bloatware and maybe just one meaningful line of code in this method, isn't it? But now suppose you also need a method that writes the file into a String
and another one that just counts the number of non-empty lines in the file itself. Instead of copying and pasting the former method another two times and changing just a single statement in each new method, I think that it could be a better idea to generalize it, by passing a closure that tells the method, case by case, how a line read from the file should be managed, as follows:
public void printFile(String fileName) {
Closure1<String> lineReader = closure(String.class);
{ of(System.out).println(var(String.class)); }
readFileByLine(fileName, lineReader);
}
public String readFile(String fileName) {
StringWriter sw = new StringWriter();
Closure1<String> lineReader = closure(String.class);
{ of(sw).write(var(String.class)); }
readFileByLine(fileName, lineReader);
return sw.toString();
}
public int countFileLines(String fileName) {
lineCounter = 0;
Closure1<String> lineReader = closure(String.class);
{ of(this).countNonEmptyLine(var(String.class)); }
readFileByLine(fileName, lineReader);
return lineCounter;
}
private int lineCounter = 0;
void countNonEmptyLine(String line) {
if (line != null && line.trim().length() > 0) lineCounter++;
}
private void readFileByLine(String fileName, Closure1<String> lineReader) {
BufferedReader reader = null;
try {
InputStream stream = getClass().getClassLoader().getResourceAsStream(fileName);
reader = new BufferedReader(new InputStreamReader(stream));
for (String line = reader.readLine();
line != null; line = reader.readLine()) {
lineReader.apply(line);
}
} catch (IOException ioe) {
throw new RuntimeException("Error while reading file " + fileName, ioe);
} finally {
try {
if (reader != null) reader.close();
} catch (IOException ioe) {
throw new RuntimeException("Error while closing file reader", ioe);
}
}
}
History
- 6th September, 2009: Initial post