简介

我们通常讲到闭包,一般都是指在javascript的环境中。闭包是JS中一个非常重要的也非常常用的概念。闭包产生的原因就是变量的作用域范围不同。一般来说函数内部的定义的变量只有函数内部可见。如果我们想要在函数外部操作这个变量就需要用到闭包了。

更多精彩内容且看:

更多内容请访问www.flydean.com

JS中的闭包

在JS中,变量可以分为两种全局作用域和局部作用域。在函数外部无法读取函数内部定义的局部变量。

  function f1(){
    var n=10;
  }
  alert(n); // error

上面的例子中,我们在函数f1中定义了一个局部变量n,然后尝试从函数外部访问它。结果出错。

虽然函数中定义的变量在函数外部无法被访问。但是在函数中定义的函数中可以访问呀。

function f1(){
    var n=10;
    function f2(){
      alert(n);
    }
    return f2;
  }
  var result=f1();
  result(); // 10

上面的例子中,我们在f1中定义了f2,在f2中访问了局部变量n。最后将f2返回。接着我们可以操作返回的函数f2来对函数中定义的局部变量n进行操作。

所以我们得出了闭包的定义:闭包就是定义在函数内部的函数,或者闭包是能够访问函数局部变量的函数。

java中的闭包

在lambda表达式出现之前,java中是没有函数的概念的。和函数差不多相当的就是方法了。

在方法内部可以定义方法的局部变量。我们无法在方法内部定义方法,但是我们可以在方法内部定义匿名类。那么这个匿名类是可以访问方法中定义的局部变量的。如下例所示:

public Runnable createClosureUsingClass(){
int count=10;
Runnable runnable= new Runnable() {
@Override
public void run() {
System.out.println(count);
}
};
return runnable;
}

在上面的方法中,我们定义了一个局部变量count。然后创建了一个匿名类runnable。在runnable中,我们访问了局部变量count。

最后将这个创建的匿名类返回。这样返回的匿名类就包含了对方法局部变量的操作,这样就叫做闭包。

Lambda表达式最佳实践中,我们介绍了lambda表达式和匿名类的不同之处在于:

在内部类中,会创建一个新的作用域范围,在这个作用域范围之内,你可以定义新的变量,并且可以用this引用它。

但是在Lambda表达式中,并没有定义新的作用域范围,如果在Lambda表达式中使用this,则指向的是外部类。

虽然this的指向是不同的,但是在lambda表达式中也是可以访问方法的局部变量:

public Runnable createClosureUsingLambda(){
int count=10;
Runnable runnable=()-> System.out.println(count);
return runnable;
}

上面的例子中,我们在lambda表达式中访问了定义的count变量。

深入理解lambda表达式和函数的局部变量

首先lambda表达式是无状态的,因为lambda表达式的本质是函数,它的作用就是在给定输入参数的情况下,输出固定的结果。

如果lambda表达式中引用的方法中的局部变量,则lambda表达式就变成了闭包,因为这个时候lambda表达式是有状态的。我们接下来用个例子来具体说明。

上面的lambda表达式创建的Runnable,我们可以这样使用:

public void testClosureLambda(){
Runnable runnable=createClosureUsingLambda();
runnable.run();
}

为了深入理解lambda表达式和局部变量传值的关系,我们将编译好的class文件进行反编译。

javap -c -p ClosureUsage

将部分输出结果列出如下:

  public java.lang.Runnable createClosureUsingLambda();
Code:
0: bipush 10
2: istore_1
3: iload_1
4: invokedynamic #12, 0 // InvokeDynamic #0:run:(I)Ljava/lang/invokedynamicinvokedynamic;
9: astore_2
10: aload_2
11: areturn private static void lambda$createClosureUsingLambda$0(int);
Code:
0: getstatic #29 // Field java/lang/System.out:Ljava/io/PrintStream;
3: iload_0
4: invokevirtual #35 // Method java/io/PrintStream.println:(I)V
7: return

上面我们列出了createClosureUsingLambda和它内部的lambda表达式的反编译结果。

可以看到在createClosureUsingLambda方法中,我们首先定义了一个值为10的int,并将其入栈。

再看lambda表达式生成的方法,我们可以看到这个方法多出了一个int参数,并且通过getstatic命令将参数传递进来。

这就是lambda表达式传递状态的原理。

总结

本文介绍了闭包和lambda表达式之间的关系,并从字节码的角度进一步说明了局部变量是怎么传递给函数内部的lambda表达式的。

本文的例子https://github.com/ddean2009/

learn-java-base-9-to-20

本文作者:flydean程序那些事

本文链接:http://www.flydean.com/java-lambda-closure/

本文来源:flydean的博客

欢迎关注我的公众号:程序那些事,更多精彩等着您!

Lambda表达式和闭包Closure的更多相关文章

  1. 浅析匿名函数、lambda表达式、闭包(closure)区别与作用

    浅析匿名函数.lambda表达式.闭包(closure)区别与作用 所有的主流编程语言都对函数式编程有支持,比如c++11.python和java中有lambda表达式.lua和JavaScript中 ...

  2. C# 从CIL代码了解委托,匿名方法,Lambda 表达式和闭包本质

    前言 C# 3.0 引入了 Lambda 表达式,程序员们很快就开始习惯并爱上这种简洁并极具表达力的函数式编程特性. 本着知其然,还要知其所以然的学习态度,笔者不禁想到了几个问题. (1)匿名函数(匿 ...

  3. lambda表达式和闭包

    lambda表达式和闭包 熟悉的Javascript或者Ruby的同学,可能对另一个名词:闭包更加熟悉.因为一般闭包的示例代码,长得跟lambda差不多,导致我也在以前很长一段时间对这两个概念傻傻分不 ...

  4. 理解Lambda表达式和闭包

    了解由函数指针到Lambda表达式的演化过程 Lambda表达式的这种简洁的语法并不是什么古老的秘法,因为它并不难以理解(难以理解的代码只有一个目的,那就是吓唬程序员) #include " ...

  5. python3 入门 (三) 函数与lambda表达式、闭包

    函数 是组织好的.可重复使用的.用来实现单一或相关联功能的代码段. 函数代码块以def关键词开头,后接函数标识符名称和圆括号() 任何传入参数和自变量必须放在圆括号中间.圆括号之间可以用于定义参数 函 ...

  6. Spark中Lambda表达式的变量作用域

    通常,我们希望能够在lambda表达式的闭合方法或类中访问其他的变量,例如: package java8test; public class T1 { public static void main( ...

  7. 【C++】C++中的lambda表达式和函数对象

    目录结构: contents structure [-] lambda表达式 lambda c++14新特性 lambda捕捉表达式 泛型lambda表达式 函数对象 函数适配器 绑定器(binder ...

  8. 1-06-2 Lambda表达式

    last modified:2020/10/31 1-06-3-Lambda表达式 6.3.1 为什么引入lambda表达式 lambda表达式是一个可传递的代码块,可以在以后执行一次或多次. 将一个 ...

  9. Java中的函数式编程(三)lambda表达式

    写在前面 lambda表达式是一个匿名函数.在Java 8中,它和函数式接口一起,共同构建了函数式编程的框架.   lambda表达式乍看像是匿名内部类的一种语法糖,但实际上,它们是两种本质不同的事物 ...

  10. 背后的故事之 - 快乐的Lambda表达式(一)

    快乐的Lambda表达式(二) 自从Lambda随.NET Framework3.5出现在.NET开发者眼前以来,它已经给我们带来了太多的欣喜.它优雅,对开发者更友好,能提高开发效率,天啊!它还有可能 ...

随机推荐

  1. 程序员应具备的PS基本技能(二):程序员切图最常使用的工具组-选择工具组

    若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...

  2. 图书管理系统---基于form组件和modelform改造添加和编辑

    添加 基于form组件改造 步骤1 1.为了区分自己写的form类和视图逻辑,所以工作中需要区分开来,那么就可以在应用下创建一个叫utils的文件夹,专门存放我们写的form类,py文件名随便起 2. ...

  3. 【Azure Redis】Redis导入备份文件(RDB)失败的原因

    问题描述 在测试Azure Redis的导入/导出备份文件的功能中,突然发现在Redis 4.0上导入的时候,一直报错. 问题解答 因为门户上只是显示导入失败,没有任何错误消息说明.根据常理推断,Re ...

  4. 【Azure 环境】Azure 流分析服务(Steam Analytics) 报出 OutputDataConversionError 错误引起延迟及超时

    问题描述 Azure 流分析服务(Steam Analytics) 报出 OutputDataConversionError 错误引起延迟及超时. 查看详细错误: 问题解答 在错误消息中,有非常明确的 ...

  5. 【Azure 存储服务】调用REST API获取Stroage Account Table中所有的Entity计数 -- Count

    问题描述 在Storage Account的使用中,如果想获取Table中全部Entity的计数以及大小,如果是REST API方式,如何来获取呢? 问题解答 在Azure中,所有服务的Metrics ...

  6. MongoDB可视化compass 连接数据库失败Invalid UTF-8 string in BSON document

    An error occurred while loading navigation: Invalid UTF-8 string in BSON document 出现这个问题建议降低compass版 ...

  7. 使用Kubernetes搭建带有ik分词的Elasticsearch集群

    创建好带有Ik分词的es镜像,并上传到镜像仓库中,创建镜像可参考链接中的文档 https://www.cnblogs.com/hi-lijq/p/16895206.html 编写es_cluster- ...

  8. 搭建一个Java项目可直接拿去使用的通用工具类

    1.通用枚举类 import lombok.Getter; /** * @Description 状态码定义约束,共6位数,前三位代表服务,后3位代表接口 * 比如 商品服务210,购物车是220.用 ...

  9. Zabbix“专家坐诊”第181期问答汇总

    问题一 Q:大佬们,有没有基础的 监控模板 触发器分享下? A:你可以试一下乐维免费版(https://forum.lwops.cn/download ),里面基本的模板全齐. 问题二 Q :orab ...

  10. Dashboard、Rancher与KubeSphere对比

    在容器技术和微服务架构日益盛行的今天,对于容器编排和管理平台的选择显得尤为重要.Kubernetes(K8s)作为容器编排的事实标准,其生态系统中涌现出了许多管理和监控工具.其中,Dashboard. ...