延遲計算和閉包

前言

一直覺得函數(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ù):

  1. 接受一個或多個函數(shù)作為輸入
  2. 輸出一個函數(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)方式集嵌。反正我是這么理解了閉包的,自從這樣想明白之后御毅,我突然變得會使用閉包了
?? 以上

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末根欧,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子端蛆,更是在濱河造成了極大的恐慌凤粗,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件今豆,死亡現(xiàn)場離奇詭異嫌拣,居然都是意外死亡,警方通過查閱死者的電腦和手機呆躲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進店門异逐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人插掂,你說我怎么就攤上這事灰瞻。” “怎么了辅甥?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵酝润,是天一觀的道長。 經(jīng)常有香客問我璃弄,道長要销,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任夏块,我火速辦了婚禮疏咐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘脐供。我一直安慰自己凳鬓,他們只是感情好,可當我...
    茶點故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布患民。 她就那樣靜靜地躺著缩举,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上仅孩,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天托猩,我揣著相機與錄音,去河邊找鬼辽慕。 笑死京腥,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的溅蛉。 我是一名探鬼主播公浪,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼船侧!你這毒婦竟也來了欠气?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤镜撩,失蹤者是張志新(化名)和其女友劉穎预柒,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體袁梗,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡宜鸯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了遮怜。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片淋袖。...
    茶點故事閱讀 38,654評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖锯梁,靈堂內(nèi)的尸體忽然破棺而出即碗,到底是詐尸還是另有隱情,我是刑警寧澤涝桅,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布拜姿,位于F島的核電站烙样,受9級特大地震影響冯遂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜谒获,卻給世界環(huán)境...
    茶點故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一蛤肌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧批狱,春花似錦裸准、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春权悟,著一層夾襖步出監(jiān)牢的瞬間砸王,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工峦阁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留谦铃,地道東北人。 一個月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓榔昔,卻偏偏與公主長得像驹闰,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子撒会,可洞房花燭夜當晚...
    茶點故事閱讀 43,543評論 2 349

推薦閱讀更多精彩內(nèi)容