闭包之Java lambda表达式浅析

1.首先,什么是闭包

先看这么一个图:

G,F,N 分别代表三个层次的函数,a,b,c分别是其中的变量。正常情况下,G无法访问到N中的c。但是,通过闭包G可以访问到c。这是一般对闭包的初体验,下面看看维基对闭包的定义。

维基的定义:在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。

看起来意见还不统一,总结一下:

1.闭包是一个函数,它引用了自由变量并和这个函数包在一起。

2.闭包不是函数,他是函数和引用环境组成的实体。

这两个定义其实差不多,只是在纠结闭包是否是一个函数。第二种较为全面,理由是函数是一些可执行的代码,这些代码在编写时就定义了并且只有一个实例,但是闭包会因为引用的变量不同而产生不同的实例。因此闭包和函数还是有差别的,它只是像函数,但并不是函数。

以python为例说明闭包有多个实例:


这个例子里,函数make_add返回一个lambda,lambda可以通过不同的参数来生成不同的闭包实例,其中的参数就是引用变量,当然引用变量不止这些,还包括所有能引用到的变量,这些引用变量被称为上值(upvalue)。

构成闭包其实只要有两点:

1.函数的代码逻辑

2.引用环境

引用环境是什么呢?

引用环境是一组变量的定义

identifer----value

identifer----value

identifer----value

.......

以上就是一个引用环境,词法作用域的语言中,在函数取值时,会捕捉函数定义时能引用到的所有变量,将其组成一个新的引用环境。和这个函数一起,就组成了闭包。由于每种语言实现引用环境有所不同,对闭包的理解便有所不同。在回到开始这个例子,N为了维持它的词法域,将他所有能访问到的局部变量都包含在了一起。

2.闭包的条件

从上面的定义可以得知

1.函数可以嵌套定义

2.可以定义匿名函数

3.upvalue

4.函数和upvalue可以组成一个可调用的实体。

5.函数是一种数据类型,可以赋值,可以作为参数,也可以做为返回值。

3.Java中的闭包

从上面的闭包条件可知,闭包几乎是函数式编程的专利,Java作为强制的面向对象编程语言怎么可能有闭包,先不管!看看Java是如何实现闭包的。

a.语法

首先要声明一个函数式接口,比如Runnable:

@FunctionalInterface

public interface Runnable {

public abstract void run();

}

函数式接口只允许有一个函数,如果你有一个接口,并且这个接口只有一个方法,即便你没有@FunctionalInterface,编译器也会认为这是一个函数接口。但是建议还是@FunctionalInterface一下,如果其他人在这个接口下添加方法,IDE会告诉他:你这么做是非法的。

有了这个函数式接口就可以用Lambda表达式了,格式如下

(参数) -> 表达式

如果没有参数也可以简化为(),如果只有一个参数,可以不要括号

表达式需要用{}块起来,如果只有一行代码则不需要。

比如:

new Thread(() -> System.out.println("Hello world!"));

1.  () -> 5                          // return都不要写

2.  x -> 2 * x            // 括号都可以不要

3.  (x, y) -> x – y                    // 参数声明也可以免写

4.  (int x, int y) -> x + y      // 你也可以声明

5.  (String s) -> System.out.print(s) // 大括号也懒得打了

还可以用::两个冒号来简写

1.System.out::println    //非静态方法引用

2.String::valueOf    //静态方法引用

3.ArrayList::new    //构造函数的引用

b.特性

特性1:不允许修改局部变量

比如以下写法是不允许的

int n= 1;

new Thread(() -> n++);

提示:Local variable n defined in an enclosing scope must be final or effectively final

把n改成final就可以了,如果你不修改局部变量的值并不是需要final。如果没有局部变量的捕获,据说这样的闭包要高效一些。

特性2:参数也必须是final

特性3:可以访问并修改外部类的变量

特性4:不能将异常抛出

惊!看起来就和内部类一样!

c.分析

函数式编程中,函数是第一公民,可以被嵌套定义,也可以被当作参数使用,就像面向对象编程的类一样。再联系上文提到的闭包条件,我们发现Java作为一种强制面向对象的语言,几乎是不能有闭包的,但是如果将函数改为类,那么情况就不一样了。事实上Java 8也就是这么干的。

函数式接口规定只能有一个方法,目的在于使这个类的作用就像一个函数一样。仅此一步,闭包的条件就满足了。

d.思考

1.和内部类的异同

异:

不会生成新的类

this不同

同:

不能改变局部变量的值

可以改变全局变量的值

2.既然匿名内部类和闭包那么相同,那么内部类也可以算是一个闭包吗?

有种观点认为,匿名内部类是面向对象的闭包,理由是内部类不仅包含了代码逻辑还包含了外围类的引用,符合闭包的定义。Oracle官网也提到:

Lambda expressions to replace anonymous inner classes in Java SE 8.

他就是为了解决匿名类的。

另一个佐证:在Java还没支持闭包之前,就有人提出修改匿名内部类的创建方式来实现闭包。

3又是一个语法糖?

并不是,同样一个接口,用内部类实现,会多出一个class文件来,如果用闭包则不会。

4.闭包的优缺点

闭包有更加简洁的语法,更清晰的逻辑,也更加抽象。但是闭包的内存开销会大一些,也不要滥用。

e.应用

Java8的Function包有一大堆的函数式接口,以供使用

http://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html

如forEach,你现在不需要写一个函数,你只需要这样做就可以了

list.forEach(System.out::println);

方法的实现:

//Consumer是function包下的一个函数式接口。

default void forEach(Consumer action) {

Objects.requireNonNull(action);

for (T t : this) {

action.accept(t);

}

}

Stream是是目前Java Lamdba表达式的重头戏,Stream并不是IO流,而是对集合集合操作的迭代,普通的Iterator只是对集合元素一个一个的遍历,而Stream不仅实现了遍历,还包含了对元素操作的描述,如过滤,内类转换等。这些描述就是Lamdba表达式。

下面看一个官网的示例:

int sum = widgets.stream()

.filter(b -> b.getColor() == RED)

.mapToInt(b -> b.getWeight())

.sum();

上例中,我们需要获取widgets集合中颜色为红色的元素,并将宽度转换为Int再求和,这个例子看起来就像我们使用AlertDialog.Builder创建Dialog一样,每个setXX方法都返回Builder对象,最终通过create方法创建真正的Dialog。

Stream有两种形式:

1.Intermediate

Intermediate是中间操作,其后可以再跟Stream操作,如上例的filter,就像Builder一样。辨别Intermediate可以通过方法是否返回Stream来辨别,Intermediate的操作不会执行真正的遍历,它的目的是打开流,并包含了这样的操作,再返回新的流交给下一个操作使用。

2.Terminal

最终操作,其后不能再跟Stream操作,就像Create一样。Terminal操作返回值不是Stream,可能是一个数据类型或者Void,如上例的sum,返回的值是Int。因此Terminal是真正执行遍历的地方。

Strem的特性:

1.没有存储,流不是一个数据结构,是一个数据操作的管道(through a pipeline of computational operations.)。每一次值转换都生成一个新的Stream对象,因此操作可以向链条一样形成一个管道。


流的实现类是AbstractPipeline,其主要的属性有:

private Spliterator sourceSpliterator;//资源的迭代器,包含了集合

private final AbstractPipeline sourceStage;//自身Stream

private final AbstractPipeline previousStage;//上一个Stream

private AbstractPipeline nextStage;//下一个Stream

2.不修改源数据

private final Collection collection; // null OK

3.无界操作,即Short-circuiting,他的作用是在无限的集合中返回有限的流或者在有限的时间内完成操作。这一类的方法有anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit。

4.中间操作是懒惰的

5.只能执行一次最终操作

6.并行,采用fork/join框架

list.parallelStream();

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,579评论 18 139
  • 原文http://www.codeceo.com/article/learn-java-lambda.html L...
    与我常在1053阅读 1,118评论 1 7
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,559评论 18 399
  • 原文地址:http://stackoverflow.com/documentation/java/91/lambd...
    王永迪阅读 7,794评论 6 17
  • 当世界向你开火,你痛而不言, 回头望去,足迹清晰。 你竖立盔甲,保持着最后的尊严, 告诉自己,“我只是输给了时间”...
    讲真书画阅读 270评论 5 2