这周开学,上了两天感觉课好多,学校现在还停水,宿舍网络也还没通,简直爆炸,感觉能静下心看书的时间越来越少了...寒假还有些看过书之后的存货,现在写一点发出来。加上假期两个月左右都过去了书才看了1/7都不到...还得去续借一下,看来买书多看书少的毛病也得改一改,先致力于把剁手买的书啃完。

另外再次推荐下我现在看的这本书(详见第0篇),越看越费劲...干货非常多而且特别干,总之相比于其他书可以说是一页顶三页了,每一段都值得仔细琢磨,发现看不懂的还得调转方向先去填坑。

接上一篇:初探Lambda表达式/Java多核编程【3】Lambda语法与作用域

变量捕获

当使用匿名内部类并去实现其中的接口时,更多时候我们不会去访问定义在外部的变量,反而更加倾向于将其写成类似于静态方法的一种“函数”。

就如同前文中所举的键提取器键比较器之类的例子,作为单纯的行为(如Math类中的那些静态方法),不需要引入或操作任何外部量就能够达到目的。

同时在上一篇文章中我们也对Lambda之于外部变量的访问与继承有了粗浅的了解,书中这一小节的内容将使我们用更专业的术语来表述这一问题。

DoubleUnaryOperator doubleUnaryOperator = x -> Math.abs(x);
Stream.of(-0.1, 0.2, -0.3, 0.4, -0.5)
        .map(e -> doubleUnaryOperator.applyAsDouble(e))
        .forEach(e -> System.out.println(e));

此段代码中出现的所有Lambda都有一个特性,即只通过参数与返回值与外部交互:

  • x -> Math.abs(x) 接收x,返回其绝对值
  • e -> doubleUnaryOperator.applyAsDouble(e) 接收e,返回运算结果
  • e -> System.out.println(e) 接收e,无返回值

我们将这种类型的Lambda称为无捕获Lambda无状态Lambda,究其缘由应该是此种Lambda与外部或整个系统状态无关,不对外部量直接进行捕获,仅通过参数获得输入。

相反地,捕获Lambda则会访问外部量。

“捕获”指保留住Lambda对其外部环境的引用。

同样在前一篇文章中,我们介绍了Lambda与匿名内部类在访问外部变量时,都不允许有修改变量的倾向,即若:

final double a  = 3.141592;
double b = 3.141592;

DoubleUnaryOperator anotherDoubleUnaryOperator = x -> {
    a = 2; // ERROR
    b = 3; // ERROR
    return 0.0;
    };

则:

  • 无法改变final量的值
  • 不允许在Lambda表达式中修改使用的(外部)变量

相应的报错信息:

  • Cannot assign a value to final variable
  • Variable used in lambda expression should be final or effectively final

由是观之,我们将Lambda的这种变量捕获行为称之为值捕获更加确切。

在实际操作中,如果我们在Lambda体内或匿名内部类中要对外部的某些量进行操作,那么这种限制也是很容易被规避,因为即使数组是final的(即该数组不能再指向其他数组对象),里面的值依旧可变。

所以我们只要将那个需要被操作的外部变量包装进一个数组即可:

final double[] a = {3.141592};
final double[] b = {3.141592};

DoubleUnaryOperator anotherDoubleUnaryOperator = x -> {
    a[0] = 2; // COOL
    b[0] = 3; // ALSO COOL
    return 0.0;
};

也算是一个小技巧,在安卓开发中特别常见。

至于为何库的设计者如此竭力防止调用者修改外部变量,书中给出的解释是保证程序的正确性以及性能,很容易想到,如果我们将Lambda传递给另一个线程,此时如果Lambda在某一时刻修改了外部变量的值,便很容易引起多线程相关的bug。同时,我们若要解决线程安全问题,就需要给相关的外部变量上锁或使用volatile关键字,导致了计算任务分发至不同线程后的效率问题,又违背了Lambda的初衷。

关于线程安全,书中还出现了以下两个关键词:

  • 可见性问题
  • 竞态条件

看来还需要提升知识水平才能把多线程、高并发的坑给填了,现在还不大能看懂(╯-_-)╯╧╧。

抛开线程不谈,Lambda的生命周期可能比使用Lambda的方法调用的周期还要长,因此如果Lambda捕获的外部变量是可变的,还会引起与局部变量内存泄漏相关的问题。

对于常见的需要修改外部变量的场景:

final int[] sum = {0};
Stream.of(0, 1, 2, 3, 4, 5)
        .forEach(e -> sum[0] += e);

Stream给出了完美的解决方案:

sum[0] = Stream.of(0, 1, 2, 3, 4, 5)
        .mapToInt(e -> Integer.valueOf(e))
        .sum();

也可以是:

sum[0] = Stream.of(0, 1, 2, 3, 4, 5)
        .parallel()
        .mapToInt(e -> Integer.valueOf(e))
        .sum();

总之,面向并行的方式能够给我们带来更优越的性能。

小结

Lambda能够修改字段,不受final的限制:

private static class AClass {
    private String aString = "Animal Farm";

    void aMethod() {
        new Thread(() -> aString = aString.concat(" is good.")).run();
        System.out.println(aString);
    }
}

对字段aString的引用实际上由this.aString解引用而来,其中this充当了不可变局部变量的角色,进而使我们能够修改aString的值,与将外部变量包装进数组有异曲同工之妙。

本章代码:

import java.util.function.DoubleUnaryOperator;
import java.util.stream.Stream;

public class Bar {
    public static void main(String[] args) {
        DoubleUnaryOperator doubleUnaryOperator = x -> Math.abs(x);
        Stream.of(-0.1, 0.2, -0.3, 0.4, -0.5)
                .map(e -> doubleUnaryOperator.applyAsDouble(e))
                .forEach(e -> System.out.println(e));

//        final double a  = 3.141592;
//        double b = 3.141592;
//
//        DoubleUnaryOperator anotherDoubleUnaryOperator = x -> {
//            a = 2;
//            b = 3;
//            return 0.0;
//        };

        final double[] a = {3.141592};
        final double[] b = {3.141592};

        DoubleUnaryOperator anotherDoubleUnaryOperator = x -> {
            a[0] = 2;
            b[0] = 3;
            return 0.0;
        };

        final int[] sum = {0};
        Stream.of(0, 1, 2, 3, 4, 5)
                .forEach(e -> sum[0] += e);
        System.out.println(sum[0]);

        sum[0] = Stream.of(0, 1, 2, 3, 4, 5)
                .parallel()
                .mapToInt(e -> Integer.valueOf(e))
                .sum();
        System.out.println(sum[0]);

        new AClass().aMethod();
    }

    private static class AClass {
        private String aString = "Animal Farm";

        void aMethod() {
            new Thread(() -> aString = aString.concat(" is good.")).run();
            System.out.println(aString);
        }
    }
}

以及运行结果:

0.1
0.2
0.3
0.4
0.5
15
15
Animal Farm is good.

初探Lambda表达式/Java多核编程【4】Lambda变量捕获的更多相关文章

  1. 初探Lambda表达式/Java多核编程【1】从集合到流

    从集合到流 接上一小节初探Lambda表达式/Java多核编程[0]从外部迭代到内部迭代,本小节将着手使用"流"这一概念进行"迭代"操作. 首先何为" ...

  2. 初探Lambda表达式/Java多核编程【2】并行与组合行为

    今天又翻了一下书的目录,第一章在这之后就结束了.也就是说,这本书所涉及到的新的知识已经全部点到了. 书的其余部分就是对这几个概念做一些基础知识的补充以及更深层次的实践. 最后两个小节的内容较少,所以合 ...

  3. 初探Lambda表达式/Java多核编程【3】Lambda语法与作用域

    接上一篇:初探Lambda表达式/Java多核编程[2]并行与组合行为 本节是第二章开篇,前一章已经浅显地将所有新概念点到,书中剩下的部分将对这些概念做一个基础知识的补充与深入探讨实践. 本章将介绍L ...

  4. 初探Lambda表达式/Java多核编程【0】从外部迭代到内部迭代

    开篇 放假前从学校图书馆中借来一本书,Oracle官方的<精通Lambda表达式:Java多核编程>. 假期已过大半才想起来还没翻上几页,在此先推荐给大家. 此书内容及其简洁干练,如果你对 ...

  5. Java 函数式编程(Lambda表达式)与Stream API

    1 函数式编程 函数式编程(Functional Programming)是编程范式的一种.最常见的编程范式是命令式编程(Impera Programming),比如面向过程.面向对象编程都属于命令式 ...

  6. Java 函数式编程和Lambda表达式

    1.Java 8最重要的新特性 Lambda表达式.接口改进(默认方法)和批数据处理. 2.函数式编程 本质上来说,编程关注两个维度:数据和数据上的操作. 面向对象的编程泛型强调让操作围绕数据,这样可 ...

  7. Lambda&Java多核编程-5-函数式接口与function包

    从前面的总结中我们知道Lambda的使用场景是实现一个函数式接口,那么本篇就将阐述一下何为函数式接口以及Java的function包中提供的几种函数原型. 函数式接口 早期也叫作SAM(Single ...

  8. Lambda&Java多核编程-6-方法与构造器引用

    在Lambda&Java多核编程-2-并行与组合行为一文中,我们对Stream<Contact>里的每一位联系人调用call()方法,并根据能否打通的返回结果过滤掉已经失效的项. ...

  9. Java函数式编程和lambda表达式

    为什么要使用函数式编程 函数式编程更多时候是一种编程的思维方式,是种方法论.函数式与命令式编程的区别主要在于:函数式编程是告诉代码你要做什么,而命令式编程则是告诉代码要怎么做.说白了,函数式编程是基于 ...

随机推荐

  1. iOS 错误之 NSObject 、CGFloat

    需要添加 #import <Foundation/Foundation.h> #import <UIKit/UIKit.h>

  2. NSBundle介绍

    原文:http://blog.sina.com.cn/s/blog_8c87ba3b0100t89v.html bundle是一个目录,其中包含了程序会使用到的资源. 这些资源包含了如图像,声音,编译 ...

  3. Notification的功能与用法

    Notification是显示在手机状态的通知——手机状态栏位于手机屏幕的最上方,那里一般显示了手机当前的网络状态.时间等.Notification所代表的是一种具有全局效果的通知,程序一般通过Not ...

  4. swift 中使用OC第三方库(以AFNetworking为例)

    首先呢  把你需要的第三方库导入到你的项目中来  具体怎么导入 这不是这篇的重点  看上一篇 废话不多  直接上 (1)在项目中直接建一个 oc 的控制器  然后xcode会提醒你  要不要建造桥接文 ...

  5. Webx3学习笔记(2)——基本流程

    Webx3项目是运行在jetty/tomcat这种Web应用容器中的,Web应用的模式都是请求-响应的.一个请求通过浏览器发出,封装为HTTP报文到达服务端,被容器接受到,封装为HttpRequest ...

  6. UED大全

    http://www.baiduux.com/  百度UFOhttp://ued.sohu.com/  搜狐UEDhttp://ued.taobao.com/  淘宝UEDhttp://www.ued ...

  7. 常用的.net开源项目

    Json.NET http://json.codeplex.com/ Json.Net 是一个读写Json效率比较高的.Net框架.Json.Net 使得在.Net环境下使用Json更加简单.通过Li ...

  8. RedisRepository分享和纠错

    .mytitle { background: #2B6695; color: white; font-family: "微软雅黑", "宋体", "黑 ...

  9. Unity3D中如何计算场景中的三角面和顶点数

    在做游戏开发时,场景中的三角面和顶点数影响着运行效率,尤其是在手机平台上,实时的知道场景中的各项指标,对性能优化来说至关重要,下面我们来实现一个小功能,来实时计算场景中的三角面和顶点数: 如果要知道场 ...

  10. 分布式搜索之搭建Solrcloud(Solr集群)

    Solrcloud介绍: SolrCloud(solr集群)是Solr提供的分布式搜索方案. 当你需要大规模,容错,分布式索引和检索能力时使用SolrCloud. 当索引量很大,搜索请求并发很高时,同 ...