热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

第一章.java&golang的区别之:闭包

对于golang一直存有觊觎之心,但一直苦于没有下定决心去学习研究,最近开始接触golang。就我个人来说,学习golang的原动力是因为想要站在java语言之外来审视java和其它语言的区别,再
对于golang一直存有觊觎之心,但一直苦于没有下定决心去学习研究,最近开始接触golang。就我个人来说,学习golang的原动力是因为想要站在java语言之外来审视java和其它语言的区别,再就是想瞻仰一下如此NB的语言。年前就想在2019年做一件事情,希望能从各个细节处做一次java和golang的对比分析,不评判语言的优劣,只想用简单的语言和可以随时执行的代码来表达出两者的区别和底层涉及到的原理。今天是情人节,馒头妈妈在加班,送给自己一件贴心的礼物,写下第一篇对比文章:java&golang的区别之:闭包。
关于闭包到底是啥,建议参考知乎上的解释: https://www.zhihu.com/question/51402215/answer/556617311

  • java8之前的闭包
在java8之前,java其实就已经对闭包有了一定层面的支持,实现的闭包方式主要是靠匿名类来实现的,下面是java程序员经常写的一段代码:
 1 public class ClosureBeforeJava8 {
 2     int y = 1;
 3 
 4     public static void main(String[] args) {
 5         final int x = 0;
 6         ClosureBeforeJava8 closureBeforeJava8 = new ClosureBeforeJava8();
 7         Runnable run = closureBeforeJava8.getRunnable();
 8         new Thread(run).start();
 9     }
10 
11     public Runnable getRunnable() {
12         final int x = 0;
13         Runnable run = new Runnable() {
14             @Override
15             public void run() {
16  
17 System.out.println("local varable x is:" + x); 18 //System.out.println("member varable y is:" + this.y); //error 19 } 20 }; 21 return run; 22 } 23 }

上段代码的输出:local varable x is:0

在代码的第13行到第20行,通过匿名类的方式实现了Runnable接口的run()方法,实现了一部分操作的集合(run方法),并将这些操作映射为java的对象,在java中就可以实现将函数以变量的方式进行传递了,如果仅仅是传递函数指针,那还不能算是闭包,我们再注意第17行代码,在这段被封装可以在不同的java对象间传递的代码,引用了上层方法的局部变量,这个就有些闭包的意思在里面了。但是第18行被注释掉的代码在匿名类的情况下却无法编译通过,也就是封装的函数里面,无法引用上层方法所在对象的成员变量。总结一下,java8之前的闭包特点如下:

1.可以实现封装的函数在jvm里进行传递,可以在不同的对象里进行调用;

2.被封装的函数,可以调用上层的方法里的局部变量,但是此局部变量必须为final,也就是不可以更改的(基础类型不可以更改,引用类型不可以变更地址);

3.被封装的函数,不可以调用上层方法所在对象的成员变量;
  • java8里对闭包的支持

java8里对于闭包的支持,其实也就是lamda表达式,我们再来看一下上段代码在lamda表达式方式下的写法:

 1 public class ClosureInJava8 {
 2     int y = 1;
 3 
 4     public static void main(String[] args) throws Exception{
 5         final int x = 0;
 6         ClosureInJava8 closureInJava8 = new ClosureInJava8();
 7         Runnable run = closureInJava8.getRunnable();
 8         Thread thread1 = new Thread(run);
 9         thread1.start();
10         thread1.join();
11         new Thread(run).start();
12     }
13 
14     public Runnable getRunnable() {
15         final int x = 0;
16         Runnable run = () -> {
17              
18 System.out.println("local varable x is:" + x); 19 System.out.println("member varable y is:" + this.y++); 20 }; 21 return run; 22 } 23 }

 上面对代码输出:

local varable x is:0
member varable y is:1
local varable x is:0
member varable y is:2

在代码的第16行到第20行,通过lamda表达式的方式实现了函数的封装(关于lamda表达式的用法,大家可以自行google)。通过代码的输出,大家可以发现,在lamda表达式的书写方式下,封装函数不但可以引用上层方法的effectively final类型(java8的特性之一,其实也是final类型)的局部变量,还可以引用上层方法所在对象的成员变量,并可以在其它线程和方法中对此成员变量进行修改。总结一下:java8对于闭包支持的特点如下:

1.通过lamda表达式的方式可以实现函数的封装,并可以在jvm里进行传递;

2.lamda表达式,可以调用上层的方法里的局部变量,但是此局部变量必须为final或者是effectively final,也就是不可以更改的(基础类型不可以更改,引用类型不可以变更地址);

3.lamda表达式,可以调用和修改上层方法所在对象的成员变量;
由于还没时间分析jdk和hotspot的源码,在此只能猜测推理,第2点和第3点的情况。关于第2点:上层方法的局部变量必须是final修饰的,网上的文章大部分都是说因为多线程并发的原因,无法在lamda表达式里进行修改上层方法的局部变量,这点上我是不同意这个观点的。我认为主要原因是:java在定义局部变量时,对于基础类型都是创建在stack frame上的,而一个方法执行完毕后,此方法所对应的stack frame也就没有意义了,试想一下,lamda表达式所依赖的上层方法的局部变量的存储区(stack frame)都消失了,我们还怎么能够修改这个变量,这是毫无意义的,在java里也很难实现这一点,除非像golang一下,在特定情况下,更改局部变量的存储区域(在heap里存储)。关于第3点:实现起来就比较容易,就是在lamda表达式的对象里,创建一个引用地址,地址指向原上层方法所在对象的堆存储地址即可。
  • golang里对闭包的支持

golang里对于闭包的支持,理解起来就非常容易了,就是函数可以作为变量来传递使用,代码如下:

 1 package main
 2 
 3 import "fmt"
 4 
 5 func main()  {
 6     ch := make(chan int ,1)
 7     ch2 := make(chan int ,1)
 8     fn := closureGet()
 9     go func() {
10         fn()
11         ch <-1
12     }()
13     go func() {
14         fn()
15         ch2 <-1
16     }()
17     <-ch
18     <-ch2
19 }
20 
21 func closureGet() func(){
22     x := 1
23     y := 2
24     fn := func(){
25         x = x +y
26         fmt.Printf("local varable x is:%d y is:%d \n", x, y)
27     }
28     return fn
29 }

代码输出如下:

local varable x is:3 y is:2
local varable x is:5 y is:2

代码的第24行到27行,定义了一个方法fn,此方法可以使用上层方法的局部变量,总结一下:

1.golang的闭包在表达形式上,理解起来非常容易,就是函数可以作为变量,来直接传递;

2.golang的封装函数可以没有限制的使用上层函数里的局部变量,并且在不同的goroutine里修改的值,都会有所体现。

关于第2点,大家可以参考文章:https://studygolang.com/articles/11627  中关于golang闭包的讲解部分。

  • 总结

golang的闭包从语言的简洁性、理解的难易程度、支持的力度上来说,确实还是优于java的。本文作为java和golang对比分析的第一篇文章,由于调研分析的时间有限,难免有疏忽之处,欢迎各位指正。


推荐阅读
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 本文详细介绍了在ASP.NET中获取插入记录的ID的几种方法,包括使用SCOPE_IDENTITY()和IDENT_CURRENT()函数,以及通过ExecuteReader方法执行SQL语句获取ID的步骤。同时,还提供了使用这些方法的示例代码和注意事项。对于需要获取表中最后一个插入操作所产生的ID或马上使用刚插入的新记录ID的开发者来说,本文提供了一些有用的技巧和建议。 ... [详细]
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
  • 关键词:Golang, Cookie, 跟踪位置, net/http/cookiejar, package main, golang.org/x/net/publicsuffix, io/ioutil, log, net/http, net/http/cookiejar ... [详细]
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
  • 这篇文章主要介绍了Python拼接字符串的七种方式,包括使用%、format()、join()、f-string等方法。每种方法都有其特点和限制,通过本文的介绍可以帮助读者更好地理解和运用字符串拼接的技巧。 ... [详细]
  • HashMap的相关问题及其底层数据结构和操作流程
    本文介绍了关于HashMap的相关问题,包括其底层数据结构、JDK1.7和JDK1.8的差异、红黑树的使用、扩容和树化的条件、退化为链表的情况、索引的计算方法、hashcode和hash()方法的作用、数组容量的选择、Put方法的流程以及并发问题下的操作。文章还提到了扩容死链和数据错乱的问题,并探讨了key的设计要求。对于对Java面试中的HashMap问题感兴趣的读者,本文将为您提供一些有用的技术和经验。 ... [详细]
  • java drools5_Java Drools5.1 规则流基础【示例】(中)
    五、规则文件及规则流EduInfoRule.drl:packagemyrules;importsample.Employ;ruleBachelorruleflow-group ... [详细]
  • MySQL多表数据库操作方法及子查询详解
    本文详细介绍了MySQL数据库的多表操作方法,包括增删改和单表查询,同时还解释了子查询的概念和用法。文章通过示例和步骤说明了如何进行数据的插入、删除和更新操作,以及如何执行单表查询和使用聚合函数进行统计。对于需要对MySQL数据库进行操作的读者来说,本文是一个非常实用的参考资料。 ... [详细]
author-avatar
271216608_5d6eab
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有