闭包的概念似乎被过度解读了

闭包这个概念,广泛存在于计算机(尤其是前端编程JavaScript中)和数学领域中;不可否认它是一个非常重要的概念,但是它现在似乎变成了一个千人千面的词汇,闭包似乎就是 lambda表达式,是回调,是函数式编程,是一种更高级的封装形式,是惰性计算..... 我觉得是把使用闭包这一思想可以达到的一些效果,混为一谈,导致这个简单的概念变得如此复杂;


在数学上,集合分支中有一种集合叫做闭包集合; 就是对集合中的所有元素做一些运算操作,得到的结果仍然在集合中,那么这个计算就称为闭包集合; 比如 自然数 集合对于 加法 这种操作而言就是一个闭包;


我们把上述数学概念用一种比较模糊哲学的语言描述,一个元素(可以是对象,数值,一个计算式/Function)以及它所相关的元素(即上下文)所形成的一个整体/集合 即为一个闭包(它至少可以达成某些目的); 上述是我个人对闭包这一概念的定义,我也觉得它就是闭包含义的全部;


  • 在面向对象语言编程中,一个普通的对象是不是一个闭包? 绝大多数可能不是, 结合上述的定义,它并没有包含它的上下文; 它只是单一元素;
  • 内部类是不是一个闭包? 是,它是一个闭包!内部类的外部类是可以访问它的外部类的;内部类对象和它相关的外部类对象,以及构造它的函数中的那些被用到的局部变量在一起构成了一个闭包;
  • 静态内部类是不是一个闭包?我认为不是,静态内部类其实是与他的外部类独立存在;静态内部类也不需要引用任何外部类的状态,它自己也不保有状态;内部类也禁止在其内部创建静态静态变量,我觉得这并非是编译技术上的限制,而是如果允许创建静态内部类是对闭包语义的错误理解,静态变量不能视作当前执行的上下文(它不是动态的);
  • JavaScript 返回的function 闭包有什么特殊之处?我认为没有特殊之处,它符合上面的定义,返回这个function本身 和 以及 它相关的上下文元素(在javascript中好像叫做调用链);这与java 的内部类所达成的效果在概念上是一致的,但是因为实现的手法不同,所以导致用起来会有些许差异(比如 Java的内部类所使用的外部元素必须定义/实质为final类型,这是由java实现这一内部类闭包功能时手法所收到的限制);
  • 函数回调/惰性执行 与 闭包? 我觉得他们都是可以用闭包这一技术而能达成的效果(不用闭包也能达成);一般的函数/表达式,调用它时,即要执行它的功能,它要返回我结果;而一些函数,它返回我的一个元素(是函数,是对象(内部类)并不重要)以及执行它所需要的上下文,交给调用者恰当的时机调用它即可;最典型的java Builder模式构建对象,可以将它理解为惰性构建,但是Builder对象不是闭包;
  • 函数式编程/lambda表达式: 我认为没关系,它们只是在编程写法上的一次改进,简化了写法,也便于阅读者理解;一般我们写闭包都是一些简单的执行内容,所以天然适合使用lamdba表达式;
  • Java的假闭包: 这是因为Java 内部类实现闭包的手法导致它的闭包是受限的(最典型就是必须内部类只能使用申明为final 类型成员变量)。虽然实现上并不是那么闭包(上下文是隐含的传入构造体,构造的还是普通对象),但是在我看来,它还是闭包,它满足了保存当前元素,以及它上下文元素的闭包含义;
  • 闭包导致内存泄漏:内存泄漏 和 (局部)变量升格(即提升变量的可见度,和生命周期)是闭包同一特性的一体两面。只要使用闭包,内存泄漏的风险是一定存在的,这是由于闭包的天然特性所决定。如定义,闭包是该元素以及它相关的上下文元素在一起;只要改元素还在栈上,那么它的相关上下文也就还在被引用着,因为没法被回收;下面举几个例子;通过几个例子,也顺便理解一下lamdba & inner class的异同,以及简单了解一下编译器编译实现它们的一些机制细节;
    • 当闭包内部没有引用任何外部变量时;lambda 和 inner class表现不同;
public class MomeryLeak {
    Integer[] bigdata = new Integer[1024 * 1024 * 100]; // 400m
    Integer[] bigdata2 = new Integer[1024 * 1024 * 100]; // 400m
    
public Consumer<Void> createArrays() {
    
        // 当使用lambda表达式时,在运行完gc以后, 空间可以正常回收;
    Consumer<Void> f = (a)->{System.out.println("hello");};
        //当使用内部类时, 在运行完gc以后,空间依然占用;可见lambda并非内部类的语法糖;
        /*Consumer<Void> f = new Consumer<Void>(){
                @Override
                public void accept(Void t) {
                    System.out.println("hello"); 
                }
            };*/
        return f;
}


public static void main(String[] args) {
    
        MomeryLeak ml = new MomeryLeak();
        
        Consumer<Void> f = ml.createArrays();
        System.out.println("Used Memory:" 
                + (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()));
        ml = null;
        System.gc();
        System.out.println("Used Memory:" 
                + (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()));
        f.accept(null);
    }
}
  • 当闭包内引用外部变量时,lambda 和 inner class表现一致;注意,其实我只引用了其中一个变量bigdata, 而另一个变量bigdata2的空间也无法回收;
public class MomeryLeak {
    Integer[] bigdata = new Integer[1024 * 1024 * 100]; // 400m
    Integer[] bigdata2 = new Integer[1024 * 1024 * 100]; // 400m
    
public Consumer<Void> createArrays() {
    

    //Consumer<Void> f = (a)->{System.out.println(bigdata);};
    
        Consumer<Void> f = new Consumer<Void>(){
                @Override
                public void accept(Void t) {
                    System.out.println(bigdata); 
                }
            };
        return f;
}


public static void main(String[] args) {
    
        MomeryLeak ml = new MomeryLeak();
        
        Consumer<Void> f = ml.createArrays();
        System.out.println("Used Memory:" 
                + (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()));
        ml = null;
        System.gc();
        System.out.println("Used Memory:" 
                + (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()));
        f.accept(null);
    }
}
  • 我稍微修改了代码 ml = null -> ml.bigdata2 = null; 无论时lamdba 还是 inner class 相关的空间都可以被正常回收;即,无论时lamdba 还是 inner class, 它持有的外部对象本身引用的copy(outter this copy), 而并非直接是成员变量引用的copy;
public class MomeryLeak {
    Integer[] bigdata = new Integer[1024 * 1024 * 100]; // 400m
    Integer[] bigdata2 = new Integer[1024 * 1024 * 100]; // 400m
    
public Consumer<Void> createArrays() {
    

        Consumer<Void> f = (a)->{System.out.println(bigdata);};
    
        /*Consumer<Void> f = new Consumer<Void>(){
                @Override
                public void accept(Void t) {
                    System.out.println(bigdata); 
                }
            };*/
        return f;
}


public static void main(String[] args) {
    
        MomeryLeak ml = new MomeryLeak();
        
        Consumer<Void> f = ml.createArrays();
        System.out.println("Used Memory:" 
                + (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()));
        //ml = null;
        ml.bigdata = null;
        System.gc();
        System.out.println("Used Memory:" 
                + (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()));
        f.accept(null);
    }
}
  • 当时局部变量时,lamdba与inner class是一致的,只有被使用的那个变量才会被占用;inner class反编译出来,你会发现这个局部变量 也会作为被传入构造体;
public Consumer<Void> createArrays() {
    
        Integer[] bigdata = new Integer[1024 * 1024 * 100]; // 400m
        Integer[] bigdata2 = new Integer[1024 * 1024 * 100]; // 400m
    
        //Consumer<Void> f = (a)->{System.out.println(bigdata); System.out.println(bigdata2);};
    
        Consumer<Void> f = new Consumer<Void>(){
                @Override
                public void accept(Void t) {
                    System.out.println(bigdata); 
                }
            };
        return f;
}


public static void main(String[] args) {
    
        MomeryLeak ml = new MomeryLeak();
        
        Consumer<Void> f = ml.createArrays();
        System.out.println("Used Memory:" 
                + (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()));
        ml = null;
    //  ml.bigdata = null;
        System.gc();
        System.out.println("Used Memory:" 
                + (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()));
        f.accept(null);
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271

推荐阅读更多精彩内容

  • 这是16年5月份编辑的一份比较杂乱适合自己观看的学习记录文档,今天18年5月份再次想写文章,发现简书还为我保存起的...
    Jenaral阅读 2,642评论 2 9
  • 本章将会介绍 闭包表达式尾随闭包值捕获闭包是引用类型逃逸闭包自动闭包枚举语法使用Switch语句匹配枚举值关联值原...
    寒桥阅读 1,535评论 0 3
  • 闭包是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代...
    莽原奔马668阅读 1,844评论 2 12
  • 接口/抽象类意义规范、扩展、回调为其子类提供一个公共的类型 封装子类中得重复内容 定义抽象方法,子类虽然有不同的实...
    MigrationUK阅读 2,133评论 1 28
  • 1.import static是Java 5增加的功能,就是将Import类中的静态方法,可以作为本类的静态方法来...
    XLsn0w阅读 1,173评论 0 2