这周开学,上了两天感觉课好多,学校现在还停水,宿舍网络也还没通,简直爆炸,感觉能静下心看书的时间越来越少了...寒假还有些看过书之后的存货,现在写一点发出来。加上假期两个月左右都过去了书才看了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. UITextField 之 失去焦点 收起键盘

    1. 代理 UITextFieldDelegate 2. 设置代理 textfield.delegate = self; 3. 代理事件处理 #pragma mark - textfiled代理 -( ...

  2. zabbix3.0.4 部署History

    [root@zabbix-Test ~]# history     1  passwd root    2  exit    3  yum  install ntpd*    4  yum  inst ...

  3. 云脉推出表格识别API接口可以自助接入

    针对如今市场上对于海量票据信息的录入需求,近期厦门云脉技术有限公司推出票据识别相关的产品与服务,更是在云脉OCR SDK开发者平台上上线表格识别API接口,供广大开发者和集成商自助接入.为了降低财务系 ...

  4. linux 自动备份脚本

    首先我在/root/backup 目录下建立一个文件夹, #mkdir /root/backup/mysqlbackup 以后在每天五点钟,就会有一个文件保存在这里. 接着新建文件 #vim /roo ...

  5. vdi、vhd、vmdk虚拟格式转换

    VirtualBox带来VBoxManager.exe,可以来转换格式. 命令如下(Windows环境,Linux版的应该也有VBoxManager这个二进制文件): VBoxManager存在于Vi ...

  6. Webx MVC分析(转)

    Webx框架:http://openwebx.org/ petstore:webx3/webx-sample/petstore/tags/3.0/petstore 编译之后:mvn jetty:run ...

  7. jquery serialize 和 console 漫谈

    serialize   获取表单中所有的数据 今天刚刚发现这个神奇的东西,顿时感觉高大上了很多,比以前一个一个用 val()取出来值  ,然后再 ajax 方便多了 代码示例 $(document). ...

  8. 强行在MFC窗体中渲染Cocos2d-x 3.6

    [前言] 把Cocos2dx渲染到另一个应用程序框架中的方法,在2.x时代有很多大神已经实现了,而3.x的做法网上几乎找不着.这两天抽空强行折腾了一下,不敢独享,贴出来供大家参考. [已知存在的问题] ...

  9. Iframe 自适应高度并实时监控高度变化的js代码

    不得不用到iframe,且被强烈要求不能让它出现滚动条!嵌入的页面肯定是高度不一的,页面中也不能出现大片空白,所以也不能写死高度!真是麻鬼烦啊!google N次 + 百度M次 + 试验了1605次之 ...

  10. Flex timer使用 keydown事件注册到stage

    Flex timer使用 keydown事件注册到stage: <?xml version="1.0" encoding="utf-8"?> < ...