简介

我们通常讲到闭包,一般都是指在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. Java JVM——4.程序计数器

    简介 JVM中的程序计数寄存器(Program Counter Register),Register的命名源于CPU的寄存器,寄存器存储指令相关的现场信息,CPU只有把数据装载到寄存器才能够运行. 这 ...

  2. vscode中跑go test时打印详细信息配置

    1.文件>首选项>设置>搜索Test Flags 2.选择在settings.json中编辑 3.添加以下内容 "go.testFlags":[ "-v ...

  3. jupyter notebook更改默认工作目录

    jupyter notebook默认配置路径:C:\Users\Administrator\.jupyter\jupyter_notebook_config.py 如果找不到配置文件,可以生成一个 j ...

  4. 【LeetCode栈与队列#06】前K个高频元素(TopK问题),以及pair、priority_queue的使用

    前 K 个高频元素 力扣题目链接(opens new window) 给定一个非空的整数数组,返回其中出现频率前 k 高的元素. 示例 1: 输入: nums = [1,1,1,2,2,3], k = ...

  5. 【Azure 应用服务】如果发现当前使用的订阅无法在China North 3 区中创建App Service服务,如何来解决这个问题呢?

    问题描述 在创建App Service服务时,突然发现无法选择China North 3区域,如何来解决这个问题呢? 问题解答 根据Azure中服务都需要在订阅中注册的原理,因为China North ...

  6. 【Azure 存储服务】关于Azure Storage Account(存储服务) 基于AAD用户的权限设定以及SAS key的管理问题

    问题描述 如何查到一个Storage Account曾经创建过多少SAS key,这些Key是否可以回收和限定?能否基于AAD身份对 Container / Folder 进行权限的设定和管理? 问题 ...

  7. Spring5课堂笔记

    Spring5 1..Spring 1.1.简介 Spring --> 春天,为开源软件带来了春天 2002,首次推出了Spring框架的雏形:interface21框架! Spring框架以i ...

  8. vscode 点击 import 的对象 from 带有 @ 不能自动跳转 - 要配置 jsconfig.json

    问题 vscode 点击 import 的对象 from 带有 @ 不能自动跳转 - 要配置 jsconfig.json 答案 根目录 创建 jsconfig.json 20220808 更新 inc ...

  9. pfx文件导出pem和私钥,更换网站域名证书

    openssl 路径: C:\Program Files\OpenSSL-Win64\bin -- 导出pem证书openssl pkcs12 -in C:\BackUp\Lightning\cert ...

  10. Android混淆后的bug日志通过mapping文件找对应行号

    背景 由于项目中提测以及线上的apk都是经过混淆处理的,因此拿到日志后也无法正常查看崩溃日志的行号 这个原因是因为混淆了文件,输出的日志是对应不上源文件的,为了正确找到行号需要用到mapping.tx ...