前言
一直覺得函數(shù)式編程中的閉包和延遲計算是很神奇的技術(shù)粒氧,因為一直不知道原理,所以也不知道如何用好他們节腐⊥舛ⅲ看過幾遍介紹,但終究是沒有摸到什么頭腦翼雀,直到一個偶然的機會饱苟,突然明白了...
一個延遲計算的例子
List<String> stringList=Arrays.asList("abc","cde","efg","ghi","ijk");
stringList.stream().map(s->s.toUpperCase()).peek(System.out::println).collect(Collectors.toList());
這是一個Java8中運用stream計算的一個例子,意思是把stringList中的所有字符串轉(zhuǎn)換成大寫的狼渊,然后輸出出來箱熬,然后放到新的List中
??有意思的是,如果代碼寫成這樣
List<String> stringList=Arrays.asList("abc","cde","efg","ghi","ijk");
stringList.stream().map(s->s.toUpperCase()).peek(System.out::println);
它是不會進行System.out.println()操作的狈邑。而如果寫成這樣
List<String> stringList=Arrays.asList("abc","cde","efg","ghi","ijk");
Stream<String> stream= stringList.stream().map(s->s.toUpperCase());
得到的stream里的字符串流還是小寫的城须,這就是所謂的延遲計算。
??其實我在這里挺討厭延遲計算的米苹,之前很不明白為什么不能直截了當?shù)慕o我計算結(jié)果糕伐,而需要進行終結(jié)操作,事實上終結(jié)操作并不是我想要的蘸嘶,只是為了應(yīng)對延遲計算不得已做的操作赤炒。這個問題先留在這氯析,下面我們先看下閉包,因為這兩個技術(shù)的原理是都來自高階函數(shù)莺褒。
閉包
Java閉包的用法
public class FirstLambdaExpression {
public String variable = "Class Level Variable";
public static void main(String[] arg) {
new FirstLambdaExpression().lambdaExpression();
}
public void lambdaExpression(){
String variable = "Method Local Variable";
String nonFinalVariable = "This is non final variable";
new Thread (() -> {
//Below line gives compilation error
//String variable = "Run Method Variable"
System.out.println("->" + variable);
System.out.println("->" + this.variable);
}).start();
}
}
這是java8中的一個閉包的例子,用這個例子的主要目的就是演示下Java也可以用閉包掩缓,為什么使用閉包,一言以蔽之遵岩,就是為了在鏈式計算中維持一個上下文你辣,同時進行變量隔離,這么說有點抽象尘执,再舉個例子
List<String> stringList=Arrays.asList("abc","cde","efg","ghi","ijk");
stringList.stream().reduce((s1,s2)->s1+s2).get();
輸出: <code>abccdeefgghiijk</code>
?? reduce()接收有兩個參數(shù)的函數(shù)舍哄,它的作用是把上一次計算的結(jié)果作為第一個參數(shù),然后把這次要計算的量作為第二個參數(shù)誊锭,然后進行計算表悬。
如果不使用閉包呢,我們將會得到下面的代碼
List<String> stringList=Arrays.asList("abc","cde","efg","ghi","ijk");
//System.out.println( stringList.stream().reduce((s1,s2)->s1+s2).get());
String temp="";
for(String s: stringList){
temp+=s;
}
System.out.println(temp);
我們需要一個中間變量來維持這個計算能進行下去丧靡。好吧蟆沫,我承認這沒有什么不可以接受的,我們之前就一直這樣寫温治。但是如果變成這樣了呢
List<String> stringList1=Arrays.asList("abc","cde","efg","ghi","ijk");
List<String> stringList2=Arrays.asList("abc","cde","efg","ghi","ijk");
//System.out.println( stringList.stream().reduce((s1,s2)->s1+s2).get());
String temp="";
for(String s: stringList1){
temp+=s;
}
String temp1="";
for(String s: stringList2){
temp+=s;
}
System.out.println(temp);
System.out.println(temp1);
對應(yīng)是使用閉包的寫法
List<String> stringList1=Arrays.asList("abc","cde","efg","ghi","ijk");
List<String> stringList2=Arrays.asList("abc","cde","efg","ghi","ijk");
System.out.println( stringList1.stream().reduce((s1,s2)->s1+s2).get());
System.out.println( stringList2.stream().reduce((s1,s2)->s1+s2).get());
從這個例子中我們看到了使用中間變量的不便性饭庞,對java來說這個中間變量一般在方法里面,不會有多大影響熬荆,但是對應(yīng)javascript來說舟山,太容易造成變量污染了,尤其是你用完這個字符串忘掉置空或者使用前忘記置空了卤恳,這就是為什么閉包的特性在javascript中是與生俱來的累盗,而在java中直到第八個版本才出現(xiàn)的原因了(開玩笑的。JavaScript是從一開始就是一種可以進行函數(shù)式編程的語言突琳,java第八版本才開始變得可以進行函數(shù)式編程幅骄,閉包是函數(shù)式編程語言必須提供的一種特性,正如例子中的reduce()函數(shù)一樣本今,能夠接收函數(shù)作為參數(shù)的語言拆座,必然也天生的實現(xiàn)了閉包)。
好了到目前為止我們已經(jīng)對閉包和延遲計算有了一點點了解冠息,那接下來我們就要探究下其實現(xiàn)原理了挪凑。在這我們先介紹一個概念高階函數(shù)
高階函數(shù)
定義
在數(shù)學(xué)和計算機科學(xué)中,高階函數(shù)是至少滿足下列一個條件的函數(shù):
- 接受一個或多個函數(shù)作為輸入
- 輸出一個函數(shù)
?? 在數(shù)學(xué)中它們也叫做算子(運算符)或泛函逛艰。微積分中的導(dǎo)數(shù)就是常見的例子躏碳,因為它映射一個函數(shù)到另一個函數(shù)。
?? 在無類型 lambda 演算散怖,所有函數(shù)都是高階的菇绵;在有類型 lambda 演算(大多數(shù)函數(shù)式編程語言都從中演化而來)中肄渗,高階函數(shù)一般是那些函數(shù)型別包含多于一個箭頭的函數(shù)。在函數(shù)式編程中咬最,返回另一個函數(shù)的高階函數(shù)被稱為Curry化的函數(shù)翎嫡。
?? 在很多函數(shù)式編程語言中能找到的 map 函數(shù)是高階函數(shù)的一個例子。它接受一個函數(shù) f 作為參數(shù)永乌,并返回接受一個列表并應(yīng)用 f 到它的每個元素的一個函數(shù)惑申。
范例
這是一個javascript 的例子, 其中函式 g() 有一引數(shù)以及回傳一函數(shù). 這個例子會打印 100 ( g(f,7)= (7+3)×(7+3) ).
function f(x){
return x + 3
}
function g(a, x){
return a(x) * a(x)
}
console.log(g(f, 7))
這是接收一個函數(shù)作為參數(shù)的例子,下面我們以一個返回一個函數(shù)的例子
function outer(){
var a=1;
var inner= function(){
return a++;
}
return inner
}
var b=outer();
console.log(b());
console.log(b());
分析
我們從javascript語言入手進行分析是因為java語言沒有辦法定義高階函數(shù)翅雏,高階函數(shù)是延遲計算和閉包的來源圈驼。順便提一句,高階函數(shù)的設(shè)計原理也并沒有多么復(fù)雜望几,以我了理解绩脆,高階函數(shù)實現(xiàn)起來大概來源于C語言的指向函數(shù)的指針,指向函數(shù)的指針也來源于匯編語言橄抹,對于這么底層的語言來講靴迫,沒有函數(shù)的概念只有代碼塊的概念,在代碼塊間跳來跳去害碾,就實現(xiàn)了函數(shù),在這里我就不展開來說了赦拘。
延遲計算
我們拿上面的返回函數(shù)的例子來講
var b=outer();
此時慌随,b是個什么?b是一個函數(shù)躺同,此時
var b=function(){
return a++;
}
在這里<b>b只是函數(shù)定義阁猜,并沒有執(zhí)行,而函數(shù)執(zhí)行的地方在于 <key>console.log(b());</key></b>
只用這一句話蹋艺,就說明了 <em>延遲計算</em> 的實質(zhì)剃袍,只定義不使用。
所以回頭來看下我們前面的Java代碼里“延遲計算”捎谨,這里就比較明了了民效,map函數(shù)和reduce函數(shù)只是接收了函數(shù),并沒有立即執(zhí)行涛救,這就是為什么需要一步終結(jié)操作了畏邢。
閉包
提到閉包不得不提另一個口號,那就是“在函數(shù)式編程中检吆,函數(shù)是編程語言中的一等公民”,每個函數(shù)都可以當做對象來使用舒萎,再舉一個例子
function a(){
var i=1;
return function () {
return ++i;
}
}
var b=a();
console.log(b());//2
var c=a();
console.log(b());//3
console.log(c());//2
可以看出b和c是隔離開的,互相不影響的蹭沛,這里我們可以類比成Java中的代碼:
class Outter{
int i=1;
public int inner(){
return this.i++;
}
}
Outter b=new Outer();
Outter c=new Outer();
System.out.println(b.inner())臂寝;
System.out.println(b.inner())章鲤;
System.out.println(c.inner());
在javascript中的寫法也可以寫成:
function Outter(){
var i=1;
var inner=function(){
return i++;
}
return inner;
}
var b=new Outter();//實際上返回一個inner對象
var c=new Outter();//實際上又返回一個inner對象
console.log(b());//1
console.log(b());//2
console.log(c());//1
console.log(c());//2
ok,到這里咆贬,基本上就能理解閉包如何使用了败徊,在我看來,閉包實際上是函數(shù)式編程的面向?qū)ο缶幊趟卣鳎蛘吆瘮?shù)式編程中面向?qū)ο蟮囊环N實現(xiàn)方式集嵌。反正我是這么理解了閉包的,自從這樣想明白之后御毅,我突然變得會使用閉包了
?? 以上