延迟计算和闭包

前言

一直觉得函数式编程中的闭包和延迟计算是很神奇的技术,因为一直不知道原理,所以也不知道如何用好他们。看过几遍介绍,但终究是没有摸到什么头脑,直到一个偶然的机会,突然明白了...

一个延迟计算的例子

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中的所有字符串转换成大写的,然后输出出来,然后放到新的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里的字符串流还是小写的,这就是所谓的延迟计算。
  其实我在这里挺讨厌延迟计算的,之前很不明白为什么不能直截了当的给我计算结果,而需要进行终结操作,事实上终结操作并不是我想要的,只是为了应对延迟计算不得已做的操作。这个问题先留在这,下面我们先看下闭包,因为这两个技术的原理是都来自高阶函数。

闭包

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()接收有两个参数的函数,它的作用是把上一次计算的结果作为第一个参数,然后把这次要计算的量作为第二个参数,然后进行计算。
如果不使用闭包呢,我们将会得到下面的代码

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);

对应是使用闭包的写法

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来说这个中间变量一般在方法里面,不会有多大影响,但是对应javascript来说,太容易造成变量污染了,尤其是你用完这个字符串忘掉置空或者使用前忘记置空了,这就是为什么闭包的特性在javascript中是与生俱来的,而在java中直到第八个版本才出现的原因了(开玩笑的。JavaScript是从一开始就是一种可以进行函数式编程的语言,java第八版本才开始变得可以进行函数式编程,闭包是函数式编程语言必须提供的一种特性,正如例子中的reduce()函数一样,能够接收函数作为参数的语言,必然也天生的实现了闭包)。

好了到目前为止我们已经对闭包和延迟计算有了一点点了解,那接下来我们就要探究下其实现原理了。在这我们先介绍一个概念高阶函数

高阶函数

定义

在数学和计算机科学中,高阶函数是至少满足下列一个条件的函数:

  1. 接受一个或多个函数作为输入
  2. 输出一个函数
       在数学中它们也叫做算子(运算符)或泛函。微积分中的导数就是常见的例子,因为它映射一个函数到另一个函数。
       在无类型 lambda 演算,所有函数都是高阶的;在有类型 lambda 演算(大多数函数式编程语言都从中演化而来)中,高阶函数一般是那些函数型别包含多于一个箭头的函数。在函数式编程中,返回另一个函数的高阶函数被称为Curry化的函数。
       在很多函数式编程语言中能找到的 map 函数是高阶函数的一个例子。它接受一个函数 f 作为参数,并返回接受一个列表并应用 f 到它的每个元素的一个函数。

范例

这是一个javascript 的例子, 其中函式 g() 有一引数以及回传一函数. 这个例子会打印 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))

这是接收一个函数作为参数的例子,下面我们以一个返回一个函数的例子

function outer(){
    var a=1;
    var inner= function(){
    return a++;
    }
    return inner
}
var b=outer();
console.log(b());
console.log(b());

分析

我们从javascript语言入手进行分析是因为java语言没有办法定义高阶函数,高阶函数是延迟计算和闭包的来源。顺便提一句,高阶函数的设计原理也并没有多么复杂,以我了理解,高阶函数实现起来大概来源于C语言的指向函数的指针,指向函数的指针也来源于汇编语言,对于这么底层的语言来讲,没有函数的概念只有代码块的概念,在代码块间跳来跳去,就实现了函数,在这里我就不展开来说了。

延迟计算

我们拿上面的返回函数的例子来讲

var b=outer();

此时,b是个什么?b是一个函数,此时

var b=function(){
    return a++;
}

在这里<b>b只是函数定义,并没有执行,而函数执行的地方在于 <key>console.log(b());</key></b>
只用这一句话,就说明了 <em>延迟计算</em> 的实质,只定义不使用。
所以回头来看下我们前面的Java代码里“延迟计算”,这里就比较明了了,map函数和reduce函数只是接收了函数,并没有立即执行,这就是为什么需要一步终结操作了。

闭包

提到闭包不得不提另一个口号,那就是“在函数式编程中,函数是编程语言中的一等公民”,每个函数都可以当做对象来使用,再举一个例子

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,到这里,基本上就能理解闭包如何使用了,在我看来,闭包实际上是函数式编程的面向对象编程,或者函数式编程中面向对象的一种实现方式。反正我是这么理解了闭包的,自从这样想明白之后,我突然变得会使用闭包了
   以上

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容