美文网首页
java8 lambda-6-变量捕获

java8 lambda-6-变量捕获

作者: 宠辱不惊的咸鱼 | 来源:发表于2019-10-01 09:23 被阅读0次

    在Java SE 7中,编译器对内部类中引用的外部变量(即捕获的变量)要求非常严格:如果捕获的变量没有被声明为final就会产生一个编译错误。我们现在放宽了这个限制——对于lambda表达式和内部类,我们允许在其中捕获那些符合有效只读(Effectively final)的局部变量。

    简单的说,如果一个局部变量在初始化后从未被修改过,那么它就符合有效只读的要求,换句话说,加上final后也不会导致编译错误的局部变量就是有效只读变量。
    Callable<String> helloCallable(String name) {
    String hello = "Hello";
    return () -> (hello + ", " + name);
    }
    对this的引用,以及通过this对未限定字段的引用和未限定方法的调用在本质上都属于使用final局部变量。包含此类引用的lambda表达式相当于捕获了this实例。在其它情况下,lambda对象不会保留任何对this的引用。
    这个特性对内存管理是一件好事:内部类实例会一直保留一个对其外部类实例的强引用,而那些没有捕获外部类成员的lambda表达式则不会保留对外部类实例的引用。要知道内部类的这个特性往往会造成内存泄露。
    尽管我们放宽了对捕获变量的语法限制,但试图修改捕获变量的行为仍然会被禁止,比如下面这个例子就是非法的:
    int sum = 0;
    list.forEach(e -> { sum += e.size(); });
    为什么要禁止这种行为呢?因为这样的lambda表达式很容易引起race condition。除非我们能够强制(最好是在编译时)这样的函数不能离开其当前线程,但如果这么做了可能会导致更多的问题。简而言之,lambda表达式对值封闭,对变量开放。
    个人补充:lambda表达式对值封闭,对变量开放的原文是:lambda expressions close over values, not variables,我在这里增加一个例子以说明这个特性:
    int sum = 0;
    list.forEach(e -> { sum += e.size(); }); // Illegal, close over values

    List<Integer> aList = new List<>();
    list.forEach(e -> { aList.add(e); }); // Legal, open over variables(更改aList 的指向是不允许的,这个与final是一致的,亲测)

    lambda表达式不支持修改捕获变量的另一个原因是我们可以使用更好的方式来实现同样的效果:使用规约(reduction)。java.util.stream包提供了各种通用的和专用的规约操作(例如sum、min和max),就上面的例子而言,我们可以使用规约操作(在串行和并行下都是安全的)来代替forEach:
    int sum = list.stream()
    .mapToInt(e -> e.size())
    .sum();
    sum()等价于下面的规约操作:
    int sum = list.stream()
    .mapToInt(e -> e.size())
    .reduce(0 , (x, y) -> x + y);
    规约需要一个初始值(以防输入为空)和一个操作符(在这里是加号),然后用下面的表达式计算结果:
    0 + list[0] + list[1] + list[2] + ...
    规约也可以完成其它操作,比如求最小值、最大值和乘积等等。如果操作符具有可结合性(associative),那么规约操作就可以容易的被并行化。所以,与其支持一个本质上是并行而且容易导致race condition的操作,我们选择在库中提供一个更加并行友好且不容易出错的方式来进行累积(accumulation)。

    相关文章

      网友评论

          本文标题:java8 lambda-6-变量捕获

          本文链接:https://www.haomeiwen.com/subject/kalkpctx.html