为什么说 java 中 lambda 表达式的闭包是伪闭包?

2,257 阅读2分钟

为什么说 java 中 lambda 表达式的闭包是伪闭包?

Lambda 表达式不依赖于任何外部实体;它们是依赖于自身参数和常量的内容。

public interface calculator{
    int calculate(int a);
}

calculator cal = (x)->2*x;
cal.calculate(5);

闭包既依赖于参数和常量,也依赖于它们的词法范围中的变量。

public interface Calculator{
    int calculate(int a);
}

int i = 2;
calculator cal = (x)->x*i;
cal.calculate(5);

在Java中,以上两者的概念都被认为是java中的lambda表达式。

在 jdk1.7 中第二个例子中的如果内部类用到了局部变量 i 如果并且不设置为 final 会报编译错误。而在java8 中的语法糖解决了这一点。那我们修改自由变量 i 的值能否通过编译呢。

public interface Calculator{
    int calculate(int a);
}

int i = 2;
calculator cal = (x)->x*i;
i++;
cal.calculate(5);

结果是编译失败了,编译器报出了如下错误。

Variable used in lambda expression should be final or effectively final

final 指的是显式得将 i 声明成 final 变量,effectively final 指的是在代码中可以不显式得指明 final 但是你却不能改变他。由此可见 java 8 中只是增加了语法糖,lambda 表达式或者是内部类中仍然不能改变自由变量。

从编译器的角度来看,以上代码其实是被编译成了

class TestClass$1
        implements Calculator {
    private final TestClass this$0;
    private final Integer paramInteger;

    TestClass$1
    (TestClass this$0, Integer paramInteger) {
        this.this$0 = this$0;
        this.paramInteger = paramInteger;
    }

    public Integer calculate(Integer a) {
        return a * this.paramInteger;
    }
}

i 被作为成员变量传入了编译出的匿名类,因此不管在外部还是内部改变了 i 的值都没有办法影响彼此的值。

从理解角度来看,在代码层面内外部的 i 看上去就是同一个变量,改动彼此都应该生效。但其实不行,因此「 Variable used in lambda expression should be final or effectively final 」。

而在闭包的意义上来看,为了维护自由变量在 lambda 表达式中和外部保持同一状态,既然改变值无法影响状态,干脆就规定只能 final 好了。

因此 java 8 中 lambda 表达式支持的闭包被诟病为一种伪闭包。

参考 [1]: www.zhihu.com/question/24… "闭包(计算机科学)是什么?" [2]: cuipengfei.me/blog/2013/0… "为什么必须是final的呢?" [3]: www.javaworld.com/article/209… "Java programming with lambda expressions" [4]: www.ibm.com/developerwo… "使用闭包捕获状态"