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

闭包这个概念,广泛存在于计算机(尤其是前端编程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);
    }
}
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

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

友情链接更多精彩内容