Using and Currying in Java

C#里面有一个常用的关键字using,它可以保证资源在使用完毕后被正常关闭,使得程序员不必手动的关闭资源.
use using:

1
2
3
4
using (Font font1 = new Font("Arial", 10.0f))
{
byte charset = font1.GdiCharSet;
}

without using:

1
2
3
4
5
6
7
8
9
10
11
12
{
Font font1 = new Font("Arial", 10.0f);
try
{
byte charset = font1.GdiCharSet;
}

finally
{
if (font1 != null)
((IDisposable)font1).Dispose();
}

}

java 7以后java里也有了类似的特性。如果一个类实现了java.lang.AutoCloseable,那么你可以使用如下的语法。

1
2
3
4
try(ClassImplementingAutoCloseable obj = new ClassImplementingAutoCloseable())
{
...
}

接下来介绍如果一个类没有实现AutoCloseable,而又想实现类似using的功能应该怎么做。
MyFile是一个在使用之后需要被关闭的资源(需要调用close方法)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class MyFile {

private final String fileName;

MyFile(String fileName) {
super();
this.fileName = fileName;
}

void close() {
System.out.println("closing file:" + fileName);
}

void print() {
System.out.println("fileName:" + fileName);
}
}

MyFileHelper是MyFile的一个工具类。
currier接收第一个参数myFile,返回一个Consumer<Consumer<MyFile>>
意思是这个consumer接收另外一个consumer作为参数(另外这个consumer才会真正的消费myFile)。
在currier的第二个参数被应用之前,它是不知道myFile会如何被消费的。
当然你也可以把consumer换成function,资源消费之后再产生一个结果。
这里用到了Currying,Currying是在函数式语言中接收单参数的函数模拟多参数的函数的技术。
使用Currying让它在语法上它更接近using,把资源的初始化和资源的处理分开。
同时,consumer和myfile两个参数能够在不同的地方被确定,它们可以被互相隔离的测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MyFileHelper {

static Consumer<Consumer<MyFile>> using(MyFile file) {
Function<MyFile, Consumer<Consumer<MyFile>>> currier = myFile -> consumer -> {
try {
consumer.accept(myFile);
} finally {
myFile.close();
}
};
Consumer<Consumer<MyFile>> curried = currier.apply(file);
return curried;
}
}
}

最后是测试代码。

1
2
3
4
5
public static void main(String[] args) {
MyFileHelper.using(new MyFile("my-file")).accept(
myFile -> myFile.print());
}
}

输出:

1
2
fileName:my-file
closing file:my-file

完整的代码可以在这里看到:
gist