一、闭包的定义。
有很多不同的人都对闭包过进行了定义,这里收集了一些。
# 是引用了自由变量的函数。这个函数通常被定义在另一个外部函数中,并且引用了外部函数中的变量。 -- <<wikipedia>>
# 是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。-- <<Java编程思想>>
# 是一个匿名的代码块,可以接受参数,并返回一个返回值,也可以引用和使用在它周围的,可见域中定义的变量。-- Groovy ['ɡru:vi]
# 是一个表达式,它具有自由变量及邦定这些变量的上下文环境。
# 闭包允许你将一些行为封装,将它像一个对象一样传来递去,而且它依然能够访问到原来第一次声明时的上下文。
# 是指拥有多个变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。
# 闭包是可以包含自由(未绑定)变量的代码块;这些变量不是在这个代码块或者任何全局上下文中定义的,而是在定义代码块的环境中定义。
在这些定义中都有一些关键字:变量、函数、上下文等,闭包在回调函数、函数式编程、Lambda表达式中有重要的应用,为了更深刻的理解闭包,我们会试图通过JavaScript、C#和JAVA的代码进行举例,不过本次的重点还是通过JAVA如何这内部类来实现闭包,以及闭包的应用。
二、JavaScript中的闭包。
在JavaScript中,闭包是通过函数的嵌套来实现,以下是一个简单的例子:
\JSClosure\Closure1.htm
<script type="text/javascript">
function f1() {
var n = 99;
function f2() {
alert(n);
}
return f2();
}
f1();
</script>
这段代码的特点:
1、函数f1()还回了函数f2()
2、函数f2()引用了f1()定义的局变量
正常来讲,我们在外部是不能操作到f1()函数内部所定义的局部变量n,但是通过变通的方法,我们在f1()函数内部定义了一个新的函数f2(),通过f2()输出其外围函数的局部变量n,f2()是f1()的内部函数,对于f2()来说其外围函数所定义的变量、函数等上下文是可以被内部函数所访问到的;最后在f1()函数中再调用f2()以在f1()被调用时触发对f2()的调用,从而把局部变量输出。 我们对照一下闭包的定义:"引用了自由变量的函数",这里的n就是定义中的自由变量,而函数f2()通过邦定自由变量n从而形式了一个闭包。
二、.NET中的闭包。
在.NET中是通过delegate委托实现闭包的,在C#2.0时代可以通过匿名方法(函数)生成,在C#3.0时代建议使用Lambda生成,但是无论版本怎么变化,其本质还是通过delegate实现,其它形式都是些语法糖。(Lambda表达式实质上还是上生成了匿名函数)。从本质上来讲,最终于生成IL代码后,delegate其实就是一个继承了System.MulticastDelegate 或 System.Delegate的类。
这里是一个匿名方法的例子:
\DelegateClosure\Program.cs
public static void TestDelegate(string url)
{
WebRequest request = HttpWebRequest.Create(url);
request.BeginGetResponse(delegate(IAsyncResult ar)
{
using (WebResponse response = request.EndGetResponse(ar))
{
Console.WriteLine("{0}: {1}", url, response.ContentLength);
}
}, null);
}
这个例子是通过WebRequest获取某个url指定网页的内容大小的示例程序,这里的BeginGetResponse方法需要接收一个委托类型的变量。
delegate void AsyncCallback(IAsyncResult ar);
我们知道,delegate本质上最终于会生成一个类,而在这个委托对象内部分别邦定了request变量和url参数,在这个例子里我们说这个匿名方法以及它所邦定的变量构成了一个闭包。
如果使用Lambda表达式,可以写成这样:
\DelegateClosure\Program.cs
public void TestLambda()
{
WebRequest request = HttpWebRequest.Create(url);
request.BeginGetResponse(ar =>
{
using (WebResponse response = request.EndGetResponse(ar))
{
Console.WriteLine("{0}: {1}", url, response.ContentLength);
}
}, null);
}
可以看到Lambda表达式的代码更为简洁,但本质上它还是生成了匿名函数。
三、JAVA中的闭包。
在JAVA中,闭包是通过“接口+内部类”实现,像C#的delegate一样,JAVA的内部类也可以有匿名内部类。我们现在就来详细认识一下JAVA内部类。
1、内部类。
顾名思义,内部类就是将一个类定义在另一个类的内部。在JAVA中,内部类可以访问到外围类的变量、方法或者其它内部类等所有成员,即使它被定义成private了,但是外部类不能访问内部类中的变量。这样通过内部类就可以提供一种代码隐藏和代码组织的机制,并且这些被组织的代码段还可以自由的访问到包含该内部类的外围上下文环境。
这里提供了一个例子展示这种机制:
/JavaClosure/src/innerclass/DemoClass1.java
public class DemoClass1 {
private int length =0;
//private|public
private class InnerClass implements ILog
{
@Override
public void Write(String message) {
//DemoClass1.this.length = message.length();
length = message.length();
System.out.println("DemoClass1.InnerClass:" + length);
}
}
public ILog logger() {
return new InnerClass();
}
public static void main(String[] args){
DemoClass1 demoClass1 = new DemoClass1();
demoClass1.logger().Write("abc");
//.new
DemoClass1 dc1 = new DemoClass1();
InnerClass ic = dc1.new InnerClass();
ic.Write("abcde");
}
}
该例子的主要功能是实现一个写日志的ILog接口,但是该接口的类被定义在DemoClass1这个外围类中了,而且这个InnerClass内部类还可以访问其外围类中的私有变量length。
1.1、.new
从上面的例子可见,InnerClass是定义在DemoClass1内部的一个内部类,而且InnerClass还可以是Private。
如何创建这个InnerClass的实例? 可以通过外围类的实例进行创建,如:
DemoClass1 dc1 = new DemoClass1();
InnerClass ic = dc1.new InnerClass();
ic.Write("abcde");
1.2、.this
如何通过this显式引用外围类的变量?通过此格式进行引用:{外围类名}.this.{变量名称}。如:
DemoClass1.this.length = message.length();
2、局部内部类。
局部内部类是指在方法的作用域内定义的的内部类。
/JavaClosure/src/innerclass/DemoClass2.java
public class DemoClass2 {
private int length =0;
public ILog logger() {
//在方法体的作用域中定义此局部内部类
class InnerClass implements ILog
{
@Override
public void Write(String message) {
length = message.length();
System.out.println("DemoClass2.InnerClass:" + length);
}
}
return new InnerClass();
}
}
因为InnerClass类是定义在logger()方法体之内,所以InnerClass类在方法的外围是不可见的。
3、匿名内部类。
顾名思义,匿名内部类就是匿名、没有名字的内部类,通过匿名内部类可以更加简洁的创建一个内部类。
/JavaClosure/src/innerclass/DemoClass3.java
public class DemoClass3 {
private int length =0;
public ILog logger() {
return new ILog() {
@Override
public void Write(String message) {
length = message.length();
System.out.println("DemoClass3.AnonymousClass:" + length);
}
};
}
}
由此可见,要创建一个匿名内部类,可以new关键字来创建。
格式:new 接口名称(){}
格式:new 接口名称(args...){}
4、final关键字。
闭包所绑定的本地变量必须使用final修饰符,以表示为一个恒定不变的数据,创建后不能被更改。
/JavaClosure/src/innerclass/DemoClass4.java
public class DemoClass4 {
private int length =0;
public ILog logger(int level) {//final int level
//final
final int logLevel = level+1;
switch(level)
{
case 1:
return new ILog() {
@Override
public void Write(String message) {
length = message.length();
System.out.println("DemoClass4.AnonymousClass:InfoLog "
+ length);
System.out.println(logLevel);
}
};
default:
return new ILog() {
@Override
public void Write(String message) {
length = message.length();
System.out.println("DemoClass4.AnonymousClass:ErrorLog "
+ length);
System.out.println(logLevel);
}
};
}
}
public static void main(String[] args){
DemoClass4 demoClass4 = new DemoClass4();
demoClass4.logger(1).Write("abcefghi");
}
}
从例子中可以看到,logger方法接受了一个level参数,以表示要写的日志等级,这个level参数如果直接赋给内部类中使用,会导致编译时错误,提示level参数必须为final,这种机制防止了在闭包共享中变量取值错误的问题。解决方法可以像例子一样在方法体内定义一下新的局部变量,标记为final,然后把参数level赋值给它:
final int logLevel = level ;
或者直接参数中添加一个final修饰符:
public ILog logger(final int level {
5、实例初始化。
匿名类的实例初始化相当于构造器的作用,但不能重载。
/JavaClosure/src/innerclass/DemoClass5.java
public ILog logger(final int level) throws Exception {
return new ILog() {
{
//实例初始化,不能重载
if(level !=1)
throw new Exception("日志等级不正确!");
}
@Override
public void Write(String message) {
length = message.length();
System.out.println("DemoClass5.AnonymousClass:" + length);
}
};
}
匿名内部类的实例初始化工作可以通过符号 {...} 来标记,可以在匿名内部类实例化时进行一些初始化的工作,但是因为匿名内部类没有名称,所以不能进行重载,如果必须进行重载,只能定义成命名的内部类。
四、为什么需要闭包。
闭包的价值在于可以作为函数对象或者匿名函数,持有上下文数据,作为第一级对象进行传递和保存。闭包广泛用于回调函数、函数式编程中。
原生java没有提供Lambda表达式,不过可以使用尝试使用Scala的Lambda:
例子1:这个是闭包不?
scala> var add = (x: Int) => x +1
scala> add(10)
例子2:
scala> var more = 1
scala> var addMore = (x: Int) => x + more
scala> addMore(10)
五、闭包的问题。
1、让某些对象的生命周期加长。
让自由变量的生命周期变长,延长至回调函数执行完毕。
2、闭包共享。
inal关键字
/JavaClosure/src/innerclass/ShareClosure.java
interface Action
{
void Run();
}
public class ShareClosure {
List<Action> list = new ArrayList<Action>();
public void Input()
{
for(int i=0;i<10;i++)
{
final int copy = i;
list.add(new Action() {
@Override
public void Run() {
System.out.println(copy);
}
});
}
}
public void Output()
{
for(Action a : list){a.Run();}
}
public static void main(String[] args) {
ShareClosure sc = new ShareClosure();
sc.Input();
sc.Output();
}
}
这个例子创建一个接口列表List<Action> ,先向列表中创建 i 个匿名内部类new Action(),然后通过for遍历读出。
因为 i 变量在各个匿名内部类中使用,这里产生了闭包共享,java编译器会强制要求传入匿名内部类中的变量添加final
关键字,所以这里final int copy = i;需要做一个内存拷贝,否则编译不过。(在c#中没有强制要求会导致列有被遍历时
始终会取 i 最大值,这是因为延迟执行引起的)
分享到:
相关推荐
JSR-335 将闭包引入了 Java 。闭包在现在的很多流行的语言中都存在,例如 C++、C# 。闭包允许我 们创建函数指针,并把它们作为参数传递。在这篇文章中,将粗略的看一遍Java8的特性,并介绍 Lambda表达式。而且将试...
Java闭包是一个重要的编程概念,尤其在Java 8及以后的版本中得到了广泛的应用。闭包是一种函数式编程特性,允许函数保留对外部环境的引用,即使该函数被作为独立实体传递或执行。在Java中,接口中的默认方法和Lambda...
Java 闭包是一种强大的编程工具,它允许程序员创建可以捕获和存储其周围环境状态的函数。在 Java 8 及以后的版本中,闭包的实现主要通过 Lambda 表达式和默认方法来体现。 Lambda 表达式是 Java 8 引入的一项重要...
It is important in distributed computer systems to identify those events (at identifiable points in time) that are concurrent, or not related to each other in time. A group of concurrent events may ...
传递闭包是图论中的一个重要概念,特别是在研究有向图的可达性问题时。Warshall算法是由美国计算机科学家Stephen Warshall于1962年提出的,它主要用于计算有向图的所有节点对之间的传递闭包。在图中,如果存在一条从...
在Java 8中,递归和闭包是两个重要的概念,它们在函数式编程中扮演着核心角色。本文将深入探讨这两个概念,并结合实际示例解释它们如何在Java 8中协同工作。 首先,让我们理解“递归”。递归是一种算法,其中函数或...
闭包是可以包含自由(未绑定)变量的代码块;这些变量不是在这个代码块或者任何全局上下文中定义的,而是在定义代码块的环境中定义。“闭包”一词来源于以下两者的结合:要执行的代码块(由于自由变量的存在,相关...
Java函数式编程中的闭包是一种强大的工具,它允许函数访问并操作其定义时的作用域内的变量,即使该函数被传递到其他上下文中。在描述的示例中,我们看到闭包是如何帮助消除代码冗余的。 当我们需要创建多个类似的...
Java中闭包简单代码示例 在 Java 中,闭包是一个非常重要的概念,它允许开发者在函数中捕捉和保存变量的值,从而实现代码的复用和模块化。在本文中,我们将通过一个简单的示例代码,来了解 Java 中闭包的定义和应用...
Java中的闭包与回调是编程领域中的两个重要概念,尤其在Java这门广泛使用的编程语言中,它们在函数式编程和异步处理中扮演着关键角色。本文将深入探讨这两个概念,以及它们如何在实际开发中应用。 首先,让我们来...
7. **有界面的Java编程**:在实际应用中,我们可能会编写Java程序来实现这些闭包运算,并提供用户界面进行交互。这可能涉及到图形用户界面(GUI)的开发,如Swing或JavaFX,用户可以输入关系矩阵,程序计算并显示...
在IT领域,尤其是在计算机科学和数据处理中,"三元闭包"是一个重要的概念,尤其在图论、数据库理论和程序设计中有着广泛的应用。这个概念源于数学,但在编程语言中,它通常与函数和作用域相关。在这个“山东大学三元...
闭包:函数内部可以调用函数外部的变量;反之,则不行 */ var r=10; function test1(){ var r2=”abc”; //alert(r) } //alert(r2);//不能访问的函数内部的r2 test1(); //嵌套的函数中也ok啦 function
闭包和回调是Java编程中的两个重要概念,它们在处理函数式编程和事件驱动编程时起着关键作用。 首先,让我们来理解闭包。在Java中,闭包是一种能够访问和修改其封闭作用域(创建它的环境)变量的能力的函数或代码块...
在Java中,你可以使用Lambda表达式或匿名内部类来创建闭包,而在JavaScript中,所有的函数都是闭包。在C#中,可以使用`Action`、`Func`等委托类型或`lambda`表达式来创建闭包。 总的来说,闭包在编程中扮演着至关...
Java闭包和Lambda Java-8-Lambdas Java 9语言功能,第二版 而且因为我喜欢TDD,所以每一个练习都从使用JUnit4进行测试开始,在如此极端的水平上,有些练习比仅包含测试要多。 内容 Lambdas 目标键入功能接口方法...
代码如下: [removed] function createArray() { var arr = new Array(); for (var i = 0; i ”); } //以上输出全部是i的最后一次的值(10),即
根据给定文件的信息,我们可以深入探讨“传递闭包”的概念及其在计算机科学中的实现方法。 ### 传递闭包的概念 传递闭包(Transitive Closure)是集合论中的一个概念,指的是在集合\(X\)上的一类特殊的二元关系。...
然而,对于某些社区,如Java,闭包的引入引发了一些争议。一些开发者认为闭包增加了语言的复杂性,破坏了简洁性,而另一些人则相信它们能带来新的设计模式和效率提升。 闭包的核心在于它可以捕获和存储其定义时的...