65.9K
CodeProject 正在变化。 阅读更多。
Home

Lambdaj 2.0 为 Java 带来(几乎)真正的闭包

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.33/5 (2投票s)

2009年9月6日

Apache

3分钟阅读

viewsIcon

14983

lambdaj 如何尝试部分弥补 Java 中闭包的缺失。

引言

闭包可能是每个 Java 程序员的工具箱中缺失的最重要的特性。一些程序员感觉不到(或不理解)对闭包的需求,而另一些程序员则有这种需求,并且可能因此正在评估向启用函数式编程的语言迁移。我们大多数人只是学会通过使用冗长且可读性差的(匿名)内部类来部分地解决这一不足。

实际上,为 Java 提供此特性的机会已经过广泛讨论,并且在一段时间内,似乎他们将通过(在我看来,很糟糕的)BGGA 规范将其引入 Java 7。最终,他们决定放弃,让 Java 开发人员仍然是闭包的孤儿。

lambdaj 的闭包

lambdaj 试图通过在其 2.0 版本中引入一项新功能来部分填补这个空白,该功能允许以其传统的 DSL 风格定义具有自由变量的一流函数,如下面的示例所示
Closure println = closure(); { of(System.out).println(var(String.class)); } 
我相信很容易理解 println 闭包的作用。特别是,var() 方法将 String 类型的自由变量绑定到闭包。此外,请注意,定义闭包行为的语句周围的花括号只是语法糖,即使您发现保留它们可以使代码更具可读性,也可以安全地删除它们。然后,您可以通过“关闭”其自由变量来调用此闭包一次
println.apply("one"); 

或多次

println.each("one", "two", "three"); 

正如您所期望的那样,最后一个语句将导致 String "one"、"two" 和 "three" 被打印在 Java 标准输出的 3 个不同行上。可以创建无类型(如前面的示例中)和强类型闭包。假设您的类有一个对两个 int 求和的方法:

public int sum(int a, int b) {
    return a + b;
} 

可以实例化一个调用此方法的闭包,如下所示

Closure2<Integer,Integer> adder = closure(Integer.class, Integer.class); {
    of(this).sum(var(Integer.class), var(Integer.class)); 
} 

虽然您可以使用任何类型和数量的变量调用第一个闭包(如果以错误的方式调用它,最终会抛出异常),但您只能通过将 2 个 int 传递给它来调用第二个闭包,正如预期的那样

int result = (Integer)adder.apply(2, 3); 

柯里化 

闭包上通常提供的另一个功能是所谓的柯里化,它允许通过固定某些自由变量的值来创建另一个闭包。例如,您可以通过对前一个闭包的第二个变量进行柯里化来创建一个具有一个自由参数的闭包,该闭包将 10 添加到任何数字,如下所示

Closure1<Integer> adderOf10 = adder.curry2(10); 

这样,通过使用值 3 调用最后一个闭包...

int result = (Integer)adderOf10.apply(3); 

...您将获得预期的 13。最后,请注意,您可以通过直接创建一个只有一个自由参数且第二个参数已固定为 10 的闭包来获得完全相同的结果,如最后一条语句所示:

Closure1<Integer> adderOf10 = closure(Integer.class, Integer.class); { 
    of(this).sum(var(Integer.class), 10); 
}

为什么闭包有用?

如果您仍然不明白闭包如何对您有用,请让我举一个稍微复杂的例子,它可以展示为什么我认为它们是概括您的代码并从而避免重复的最强大的工具。为此,让我们编写一个从类路径读取文件,然后将文件内容逐行打印到 Java 标准输出的方法

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); 	// This is actually the only 
					// meaningful statement in this method
        } 
     } 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);
         } 
     } 
}  

此方法中有很多臃肿的代码,也许只有一行有意义的代码,不是吗?但是现在假设您还需要一个将文件写入 String 的方法,以及另一个仅计算文件本身中非空行数的方法。我认为,与其复制和粘贴前一个方法两次,并在每个新方法中只更改一个语句,不如通过传递一个闭包来概括它,该闭包逐个案例地告诉该方法如何管理从文件读取的行,如下所示

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); 
        } 
     } 
}

历史

  • 2009 年 9 月 6 日:首次发布
© . All rights reserved.