第一章.java&golang的區別之:閉包
- java8之前的閉包
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對比分析的第一篇文章,由於調研分析的時間有限,難免有疏忽之處,歡迎各位指正。
第一章.java&golang的區別之:閉包