JAVA闭包 Dear 丶 2022-06-06 05:54 140阅读 0赞 一、闭包的定义。 有很多不同的人都对闭包过进行了定义,这里收集了一些。 \# 是引用了自由变量的函数。这个函数通常被定义在另一个外部函数中,并且引用了外部函数中的变量。 -- <<[wikipedia][]>> \# 是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。-- <<Java编程思想>> \# 是一个匿名的代码块,可以接受参数,并返回一个返回值,也可以引用和使用在它周围的,可见域中定义的变量。-- [Groovy][] \['ɡru:vi\] \# 是一个表达式,它具有自由变量及邦定这些变量的上下文环境。 \# 闭包允许你将一些行为封装,将它像一个对象一样传来递去,而且它依然能够访问到原来第一次声明时的上下文。 \# 是指拥有多个变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。 \# 闭包是可以包含自由(未绑定)变量的代码块;这些变量不是在这个代码块或者任何全局上下文中定义的,而是在定义代码块的环境中定义。 在这些定义中都有一些关键字:变量、函数、上下文等,闭包在[回调函数][Link 1]、[函数式编程][Link 2]、[Lambda][]表达式中有重要的应用,为了更深刻的理解闭包,我们会试图通过[JavaScript][]、[C\#][C]和[JAVA][]的代码进行举例,不过本次的重点还是通过JAVA如何这内部类来实现闭包,以及闭包的应用。 二、JavaScript中的闭包。 在JavaScript中,闭包是通过函数的嵌套来实现,以下是一个简单的例子: ![复制代码][copycode.gif] ![ExpandedBlockStart.gif][] \\JSClosure\\Closure1.htm < script type = " text/javascript " > function f1() { var n = 99 ; function f2() { alert(n); } return f2(); } f1(); < / script> ![复制代码][copycode.gif] 这段代码的特点: 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的类。 这里是一个匿名方法的例子: ![复制代码][copycode.gif] ![ExpandedBlockStart.gif][] \\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 ); } ![复制代码][copycode.gif] 这个例子是通过WebRequest获取某个url指定网页的内容大小的示例程序,这里的BeginGetResponse方法需要接收一个委托类型的变量。 delegate void AsyncCallback(IAsyncResult ar); 我们知道,delegate本质上最终于会生成一个类,而在这个委托对象内部分别邦定了request变量和url参数,在这个例子里我们说这个匿名方法以及它所邦定的变量构成了一个闭包。 如果使用Lambda表达式,可以写成这样: ![复制代码][copycode.gif] ![ExpandedBlockStart.gif][] \\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 ); } ![复制代码][copycode.gif] 可以看到Lambda表达式的代码更为简洁,但本质上它还是生成了匿名函数。 三、JAVA中的闭包。 在JAVA中,闭包是通过“接口+内部类”实现,像C\#的delegate一样,JAVA的内部类也可以有匿名内部类。我们现在就来详细认识一下JAVA内部类。 1、内部类。 顾名思义,内部类就是将一个类定义在另一个类的内部。在JAVA中,内部类可以访问到外围类的变量、方法或者其它内部类等所有成员,即使它被定义成private了,但是外部类不能访问内部类中的变量。这样通过内部类就可以提供一种代码隐藏和代码组织的机制,并且这些被组织的代码段还可以自由的访问到包含该内部类的外围上下文环境。 这里提供了一个例子展示这种机制: ![复制代码][copycode.gif] ![ExpandedBlockStart.gif][] /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 " ); } } ![复制代码][copycode.gif] 该例子的主要功能是实现一个写日志的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、局部内部类。 局部内部类是指在方法的作用域内定义的的内部类。 ![复制代码][copycode.gif] ![ExpandedBlockStart.gif][] /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(); } } ![复制代码][copycode.gif] 因为InnerClass类是定义在logger()方法体之内,所以InnerClass类在方法的外围是不可见的。 3、匿名内部类。 顾名思义,匿名内部类就是匿名、没有名字的内部类,通过匿名内部类可以更加简洁的创建一个内部类。 ![复制代码][copycode.gif] ![ExpandedBlockStart.gif][] /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); } }; } } ![复制代码][copycode.gif] 由此可见,要创建一个匿名内部类,可以new关键字来创建。 格式:new 接口名称()\{\} 格式:new 接口名称(args...)\{\} 4、final关键字。 闭包所绑定的本地变量必须使用final修饰符,以表示为一个恒定不变的数据,创建后不能被更改。 ![复制代码][copycode.gif] ![ExpandedBlockStart.gif][] /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 " ); } } ![复制代码][copycode.gif] 从例子中可以看到,logger方法接受了一个level参数,以表示要写的日志等级,这个level参数如果直接赋给内部类中使用,会导致编译时错误,提示level参数必须为final,这种机制防止了在闭包共享中变量取值错误的问题。解决方法可以像例子一样在方法体内定义一下新的局部变量,标记为final,然后把参数level赋值给它: final int logLevel = level ; 或者直接参数中添加一个final修饰符: public ILog logger(final int level \{ 5、实例初始化。 匿名类的实例初始化相当于构造器的作用,但不能重载。 ![复制代码][copycode.gif] ![ExpandedBlockStart.gif][] /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); } }; } ![复制代码][copycode.gif] 匿名内部类的实例初始化工作可以通过符号 \{...\} 来标记,可以在匿名内部类实例化时进行一些初始化的工作,但是因为匿名内部类没有名称,所以不能进行重载,如果必须进行重载,只能定义成命名的内部类。 四、为什么需要闭包。 闭包的价值在于可以作为函数对象或者匿名函数,持有上下文数据,作为第一级对象进行传递和保存。闭包广泛用于[回调函数][Link 1]、[函数式编程][Link 2]中。 原生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关键字 ![复制代码][copycode.gif] ![ExpandedBlockStart.gif][] /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(); } } ![复制代码][copycode.gif] 这个例子创建一个接口列表List<Action> ,先向列表中创建 i 个匿名内部类new Action(),然后通过for遍历读出。 因为 i 变量在各个匿名内部类中使用,这里产生了闭包共享,java编译器会强制要求传入匿名内部类中的变量添加final 关键字,所以这里final int copy = i;需要做一个内存拷贝,否则编译不过。(在c\#中没有强制要求会导致列有被遍历时 始终会取 i 最大值,这是因为延迟执行引起的) 转载网址:[http://www.cnblogs.com/chenjunbiao/archive/2011/01/26/1944417.html][http_www.cnblogs.com_chenjunbiao_archive_2011_01_26_1944417.html] [wikipedia]: http://zh.wikipedia.org/zh-cn/%E9%97%AD%E5%8C%85_%28%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6%29 [Groovy]: http://docs.codehaus.org/pages/viewpage.action?pageId=167477326 [Link 1]: http://zh.wikipedia.org/zh/%E5%9B%9E%E8%B0%83%E5%87%BD%E6%95%B0 [Link 2]: http://zh.wikipedia.org/zh-cn/%E5%87%BD%E6%95%B8%E5%BC%8F%E7%B7%A8%E7%A8%8B [Lambda]: http://zh.wikipedia.org/zh/%CE%9B%E6%BC%94%E7%AE%97 [JavaScript]: http://zh.wikipedia.org/zh/Javascript [C]: http://zh.wikipedia.org/zh/C%E2%99%AF [JAVA]: http://zh.wikipedia.org/zh/JAVA [copycode.gif]: /images/20220606/5cf3706babcf4c2ab57b6d538aba6301.png [ExpandedBlockStart.gif]: /images/20220606/1fcd4903a9eb4fd586f7bb9a80fe780e.png [http_www.cnblogs.com_chenjunbiao_archive_2011_01_26_1944417.html]: http://www.cnblogs.com/chenjunbiao/archive/2011/01/26/1944417.html
相关 Java闭包 package Test; public class Test \{ private int data=0; private class Inner 野性酷女/ 2022年07月15日 08:59/ 0 赞/ 143 阅读
相关 闭包 1、定义: 当一个内部函数被其外部函数之外的变量引用时,就形成了一个闭包 function A(){ var count = 0; 素颜马尾好姑娘i/ 2022年06月09日 09:20/ 0 赞/ 219 阅读
相关 JAVA闭包 一、闭包的定义。 有很多不同的人都对闭包过进行了定义,这里收集了一些。 \ 是引用了自由变量的函数。这个函数通常被定义在另一个外部函数中,并且引用了外部函数中的变量 Dear 丶/ 2022年06月06日 05:54/ 0 赞/ 141 阅读
相关 闭包 先来看一个题目例子,我将在下一环节出闭包讲解。。 题目描述 实现函数 makeClosures,调用之后满足如下条件: 1、返回一个函数数组 result,长度与 喜欢ヅ旅行/ 2022年06月04日 05:51/ 0 赞/ 220 阅读
相关 闭包 1.闭包的作用 1. 实现公有变量 eg:函数累加器 1. 可以做缓存(存储结构) function eater(){ var f 落日映苍穹つ/ 2022年06月04日 04:05/ 0 赞/ 235 阅读
相关 闭包 写在前面 > 其实闭包这个词在很多的地方都听到过,以前看到说这是前端javascript这门语言的所具有的特点。当时不知所以然也没接触到对应的概念,现在遇到一个问题就是, 桃扇骨/ 2022年05月15日 03:14/ 0 赞/ 275 阅读
相关 闭包 JS闭包 > 闭包是函数和声明该函数的词法环境的组合 从本质上讲,闭包是外部函数和内部函数链接的桥梁;也就是在一个作用域中可以访问另一个作用域的变量,外部函数作用 朴灿烈づ我的快乐病毒、/ 2022年04月03日 15:54/ 0 赞/ 268 阅读
相关 闭包 闭包 1. 函数引用 def test1(): print("--- in test1 func----") \调用函数 test1() \引用函数 桃扇骨/ 2022年02月20日 00:35/ 0 赞/ 293 阅读
相关 闭包 在函数内定义个函数,并将函数作为结果返回。返回的函数可以调用外部函数的参数,如果该函数被执行,将返回已经结算出的值 <!DOCTYPE html> <html 古城微笑少年丶/ 2022年02月01日 09:01/ 0 赞/ 295 阅读
相关 闭包 闭包: 就是能够读取其他函数内部变量的函数或者说闭包就有权访问另一个函数作用域中的变量的函数。 作用: 1.可以读取函数的变量 2.让这些变量的值始终保存在内存中 3 墨蓝/ 2021年10月01日 06:58/ 0 赞/ 427 阅读
还没有评论,来说两句吧...