Java闭包

来源:互联网 发布:开淘宝店要交保证金吗 编辑:程序博客网 时间:2024/06/07 01:49

声明

本文会大致讲解下java 闭包(请允许我这么称呼Closure,因为javascript好多书都是这么翻译的),本文的内容是从Understanding the closures debate摘抄翻译出来的,如果你英文够好并且耐心充足,推荐你去阅读原文,以便获得更好的上下文语境。

什么是闭包

本质上,闭包是一段可以被传递进函数执行的代码。某种程度上,他和java内部类的概念相似。但是闭包包含更多内容。

为了更好的理解java闭包的概念,可以考虑如下情景,你想传递一段代码到一个forEach循环函数去执行:一个forEach函数循环地对每个forEach中的对象执行你传入的代码。你会如何去做,目前来说你会定义一个接口,我们就叫Block,之后呢将该接口的实现传递进forEach方法中,就像下面的例子所作的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public interface Block<T> {
  voidinvoke(T arg);
}
 
public class Utils {
  publicstatic <T> voidforEach(Iterable<T> seq, Block<T> fct) {
    for(T elm : seq)
    fct.invoke(elm);
  }
}
 
public class Test {
  publicstatic void main(String[] args) {
    List<Integer> nums = Arrays.asList(1,2,3);
    Block<Integer> print =new Block<Integer>() {
      publicvoid invoke(Integer arg) {
        System.out.println(arg);
      }
    };
    Utils.forEach(nums,print);
  }
}

就目前要把一段函数传进另一个方法去执行,我们不可避免的会使用接口,然后将接口的实现传进相应的方法中去立刻或者延迟,同步或者异步执行。闭包将会通过更加精炼的语法简化这一过程,从而移除一些java的冗余。除了带来更加精炼和可读的语法,闭包还会将会带来java全新的功能,比如定制化的控制结构。

闭包vs内部类

在近些年的发布的blog中,java之父James Gosling讨论了java闭包的历史

Closures were left out of Java initially more because of time pressures than anything else. In the early days of Java the lack of closures was pretty painful, and so inner classes were born: an uncomfortable compromise that attempted to avoid a number of hard issues. But as is normal in so many design issues, the simplifications didn't really solve any problems, they just moved them.

"最初闭包没有包括在java语言当中主要是因为时间紧迫。早些年java语言对于缺少闭包功能相当痛苦,所以内部类就诞生了:一个试图解决一些难题的不舒服的承诺。尽管内部类的应用已经十分正常,但是内部类这种简化方式并没有解决任何问题,只是简单移走了问题。

Gosling十分准确地描述了我们当下的问题:从JDK1.1版本诞生的11年来,我们一直用内部类来代替闭包实现功能。他们的确帮助我们很多,但是内部类并没有真正代替闭包。内部类只是没有他们该有的强大和简洁。

三个闭包提议(BGGA, FCM, CiCE)对比

按照复杂度排序 BGGA->FCM(First Class MEthods) plus JCA(Java control abstraction)->CiCE(Concise Instance Creation Expression) plus ARM(Automatic Resource Management)

闭包提议实践

所有的这些闭包提议都致力于简化将一段功能传递进方法的过程。让我们看看不同的提议是如何实现的

CICE (Concise Instance Creation Expression)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface Block<T> {
  voidinvoke(T arg);
}
public class Utils {
  publicstatic <T> voidforEach(Iterable<T> seq, Block<T> fct) {
    for(T elm : seq)
    fct.invoke(elm);
  }
}
public class Test {
  publicstatic void main(String[] args) {
    List<Integer> nums = Arrays.asList(1,2,3);
    Block<Integer> print
      = Block<Integer>(Integer arg) { System.out.println(arg); };
    Utils.forEach(nums,print);
  }
}

我们创建了一个闭包而不是一个内部类。本质上这个提议最终演化成一个更加简洁的语法,因为我们可以用闭包这个更加简洁的语法替换掉内部类能实现的功能。闭包的语法适用于所有只有一个方法的接口,比如Runnable, Callbable...CICE实现了简化这些接口的使用。

BGGA

此提议向Java语言引入了一个全新的类型,即函数类型(function types)。一个函数类型标识闭包。通过函数类型我们不再需要Block这个接口了(在我们的例子中), 而是使用函数类型{T => void},这个标记的意思是一个返回值为void的函数接受一个类型为T的参数。

1
2
3
4
5
6
class Utils {
  publicstatic <T> voidforEach(Iterable<T> seq, {T => void} fct) {
    for(T elm : seq)
    fct.invoke(elm);
  }
}

所有闭包都隐式地含有一个invoke方法。本质上你可以理解为编译器底层将这个函数类型合成为一个和Block一样的接口。

我们用一个比较相似的函数类型{Integer => void}创建一个闭包,并将其传入forEach方法中。

1
2
3
4
5
6
7
8
class Test {
  publicstatic void main(String[] args) {
    List<Integer> nums = Arrays.asList(1,2,3);
    {Integer =>void } print
       = { Integer arg => System.out.println(arg); };
    Utils.forEach(nums,print);
  }
}

变量print指向一个函数类型(即闭包)。创建的闭包接入一个Integer类型的参数,并将其打印出来。接着将这个闭包传递进forEach方法中打印每一个nums数组中的元素。

FCM

此提议更进一步并没有加入函数类型(function types)来标识闭包的类型,而是标识总的方法类型。在此提议下,闭包仅仅是一个方法的特例,即匿名内部方法(一个没有名字的方法)。当然,这种语法也是不同的。这种提议下函数类型用#(void(T))标识,闭包则用#(Integer arg) {System.out.println(arg); }标识

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Utils {
  publicstatic <T> voidforEach(Iterable<T> seq, #(void(T)) fct) {
    for(T elm : seq)
    fct.invoke(elm);
  }
}
class Test {
  publicstatic void main(String[] args) {
    List<Integer> nums = Arrays.asList(1,2,3);
    #(void(Integer)) print
      = #(Integer arg) { System.out.println(arg); };
    Utils.forEach(nums,print);
  }
}

从这个例子可以看出,这种提议用不一样的语法来实现其功能(通过将一段功能传递进一个方法中),但是底层的理念是相似的。下面我们将比较下不同方式如何实现闭包转换和类型兼容的。

闭包转换(Closure conversion)

这三总提议都允许一定程度上的闭包和接口间的类型兼容:一个闭包可以传递进一个兼容类型的接口类型上。这是一个十分重要的属性,因为它实现了闭包对当下内部类的替换可能。这种属性提供了对接口(只有一个方法定义,包括继承)和内部类的向后兼容性。

闭包转换的例子

考虑下一个用Runable接口实现的闭包转换,我们将一个闭包传递给一个类型为Runnable接口的变量。在当前的Java语言中我们会用内部类来实现快速的(on-the-fly)Runnable接口实现。

1
2
Runnable r = new Runnable() { public void run() { System.out.println("Hello World."); } };
new Thread(r).start();

CICE下,可以通过一个更加简洁的语法实现同样的事。所以我们对CICE下闭包对接口的类型兼容并不感到惊讶。

1
2
Runnable r = Runnable() { System.out.println("Hello World."); };
new Thread(r).start();

在BGGA 和FCM下,闭包和接口的类型转换就没那么明显了。

1
2
3
4
5
6
7
8
9
//BGGA
Runnable r = { => System.out.println("Hello World."); };
new Thread(r).start();
 
 
```java
//FCM
Runnable r = # { System.out.println("Hello World."); };
new Thread(r).start();

简洁,便利的语法标注是三种提议都努力争取的目标。

非本地控制声明(Non-local control statements)

尽管在很多方面三个提议是十分相似的,但是在一个方面三个提议分歧却很大。 BGGA下,它允许闭包的控制语句影响闭包所定义的上下文环境(context),而不仅仅是闭包内部实现的环境。下面代码展示了一个non-local return 在forEach循环的应用。这个闭包会打印(print)它每接到的一个参数,当接到的参数是3时,会立即中断main方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//BGGA notation
class Utils {
  publicstatic <T> voidfor<each(Iterable<T> seq, {T =>void } fct) {
    for(T elm : seq)
    fct.invoke(elm);
  }
}
 
class Test {
  publicstatic void main(String[] args) {
    List<Integer> nums = Arrays.asList(1,2,3,4,5);
    {Integer =>void } print = { Integer arg ==>if (arg == 3) return; System.out.println(arg); };
    Utils.forEach(nums,print);
  }
}

上面代码将在闭包接收到3参数时,中断闭包和闭包定义所在的外部的main方法的执行流程。

非本地控制声明只有BGGA提供了实现,CICE和FCM下return等控制语句只是中断闭包内部的执行流程,并不会影响闭包所定义位置的外部环境。

BGGA需要非本地return声明因为它的目标就是要通过闭包去掉常用的控制结构,我们会在下面进一步描述。这后面的想法是闭包的行为在它定义的外部环境中和一段代码一样。就是闭包可以获取它外部(enclosing scope)的变量。

语义绑定(Lexical binding)

BGGA应用如下的语义绑定

  • 包含域中的变量
  • this
  • break, continue, return

闭包中的语义绑定意味着闭包可以访问闭包外部域的变量。

this关键字的语义绑定是java语言带来的新的特性之一。然而在觅名类中,this却指向内部类中的属性值,而不是外部类的属性。在闭包中,CICE使用和传统内部类相似的做法,this指向闭包中的变量值,而BGGA和FCM使用this指向闭包外部域的变量。

三个提议最基本的分歧就是控制语句的语义绑定。BGGA中,不光是return语句,break,continue都被语义绑定了。只有BGGA提议这种特性。CICE和FCM都没有。

BGGA需要控制语句的语义绑定来移除掉普遍的控制代码。反观,FCM提议主要强调的是对函数式编程提供良好的支持,而不是移除普遍的控制代码。而FCM提案只有this关键字的语义绑定,break, continue, return关键字并没有,FCM这样做基于很好的理由-非本地控制语句声明有它的陷阱,一会我们将看到。

BGGA闭包下返回一个值(Returning a value from a BGGA closure)

原链接:http://daywasted.diandian.com/post/2013-03-07/40048655899

英文链接:http://www.javaworld.com/article/2077869/scripting-jvm-languages/understanding-the-closures-debate.html

0 0
原创粉丝点击