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

对于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对比分析的第一篇文章,由于调研分析的时间有限,难免有疏忽之处,欢迎各位指正。

原文地址:https://www.cnblogs.com/mantu/p/10381316.html

时间: 2024-07-31 10:25:23

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

Java基础知识二次学习-- 第一章 java基础

基础知识有时候感觉时间长似乎有点生疏,正好这几天有时间有机会,就决定重新做一轮二次学习,挑重避轻 回过头来重新整理基础知识,能收获到之前不少遗漏的,所以这一次就称作查漏补缺吧!废话不多说,开始! 第一章  JAVA简介 时间:2017年4月24日10:23:32 章节:01章_02节 内容:jdk的配置与安装 完成情况:已经完成,cmd中javac提示出相关命令 时间:2017年4月24日10:30:39 章节:01章_04节 内容:输出HelloWorld 完成情况: 已经完成 javac先将

《大道至简》第一章JAVA语言伪代码

第一章写了编程的精义详细写出了编程是简单的.举愚公移山的例子,既写出了我们中华文化源远流长,博大精深,千百年前就有了编程的思想,也引出了结构概念,虽我之死,有 存焉",这里描述了可能存在的分支结构,即"IF"条件判断,以及子子孙孙无穷匮也等循环结构,等编程思想.关于我会不会写程序的问题书里面也做了详细介绍!除了先天智障或后天懒惰者,都是可以学会写程序的,也许会给学编程的学生增加了很大的信心. 下面是源代码................... import.java.大道至简

《大道至简》第一章java伪代码分析

import java.大道至简第一章.*;package 编程的精义;public class 编程的精义{public static void 愚公(){System.out.println("愚公移山");--山;}public static void 子(){System.out.println("子移山");--山;}public static void 孙(){System.out.println("孙移山");--山;} publi

第一章 Java工具类目录

在这一系列博客中,主要是记录在实际开发中会常用的一些Java工具类,方便后续开发中使用. 以下的目录会随着后边具体工具类的添加而改变. 浮点数精确计算 第二章 Java浮点数精确计算

Java第一章----Java概述+环境搭建

写在前面的话: Java基础的东西看过好几遍,但是过一段都就忘记了,所以这次我决定花费一些时间整理一个系列博客供以后方便查阅.此系列根据Java编程思想+Java核心技术两本书整理而来,这两本书也是我极力推荐大家看的两本,因为每次看都有不同的收获,两本横向看相辅相成定会让你受益匪浅,好了敬请期待吧! 第一节:Java简介 Java是由Sun公司在1995年5月推出的一种面向对象的编程语言,极好的实现了面向对象理论,更加注重对象的本身不用太关注事件的过程. Java通过Java编程语言+Java类

《Java虚拟机精讲》读书笔记-第一章Java体系结构

本章主要讲解了java体系的结构,包括四个方面:java编程语言,字节码,Java API和java虚拟机四部分 并简单介绍了以上四部分,同时对java中的一些新特性进行了介绍,由于我阅读本书的时候java8已经发布,因此其中的一些说是要在后续版本实现的功能已经实现了,如lambda表达式,函数式编程等,最后介绍了OpenJdk的使用和编译 下面对一些看书之前不了解的概念进行学习 lambda表达式 什么是λ表达式 λ表达式本质上是一个匿名方法.让我们来看下面这个例子: public int a

第一章 Java代码执行流程

说明:本文主要参考自<分布式Java应用:基础与实践> 1.Java代码执行流程 第一步:*.java-->*.class(编译期) 第二步:从*.class文件将其中的内容加载到内存(类加载)(运行期) 第三步:执行代码(运行期) 2.代码编译 javac命令将源码文件编译为*.class文件. 后边将介绍: javac将*.java编译成*.class文件的过程 class文件的文件格式,以及其存储的内容 3.类加载 主要是指将*.class文件加载到JVM,并形成Class对象的机

《大道至简》第一章java伪代码

import java.大道至简.*; import  java.io.*; //第一·<汤问篇>愚公移山 import.java.愚公移山.*; public class YuGongYiShan{ public static void main(String [] args) throws IOException{ 第一节 String person="愚公"; if(愚公死) 儿孙移山 else 愚公自己移山 While(子孙存在) { 子又生孙,孙又生子:--子子孙

第一 章 Java简介

1.JDK的安装与配置 (1)直接安装JDK(java开发工具包,内含JRE) (2)配置环境变量:我的电脑——右键属性——高级——环境变量——系统环境变量——path——编辑(将JDK的安装路径配置到path属性之中) (3)启动命令行方式:配置成功后,在运行界面输入“cmd”,输入javac 2.第一个程序 1 public class HaHa{ 2 public static void main(String args[]){ 3 System.out.println("我java&qu