初探Lambda表达式/Java多核编程【4】Lambda变量捕获
这周开学,上了两天感觉课好多,学校现在还停水,宿舍网络也还没通,简直爆炸,感觉能静下心看书的时间越来越少了...寒假还有些看过书之后的存货,现在写一点发出来。加上假期两个月左右都过去了书才看了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变量捕获的更多相关文章
- 初探Lambda表达式/Java多核编程【1】从集合到流
从集合到流 接上一小节初探Lambda表达式/Java多核编程[0]从外部迭代到内部迭代,本小节将着手使用"流"这一概念进行"迭代"操作. 首先何为" ...
- 初探Lambda表达式/Java多核编程【2】并行与组合行为
今天又翻了一下书的目录,第一章在这之后就结束了.也就是说,这本书所涉及到的新的知识已经全部点到了. 书的其余部分就是对这几个概念做一些基础知识的补充以及更深层次的实践. 最后两个小节的内容较少,所以合 ...
- 初探Lambda表达式/Java多核编程【3】Lambda语法与作用域
接上一篇:初探Lambda表达式/Java多核编程[2]并行与组合行为 本节是第二章开篇,前一章已经浅显地将所有新概念点到,书中剩下的部分将对这些概念做一个基础知识的补充与深入探讨实践. 本章将介绍L ...
- 初探Lambda表达式/Java多核编程【0】从外部迭代到内部迭代
开篇 放假前从学校图书馆中借来一本书,Oracle官方的<精通Lambda表达式:Java多核编程>. 假期已过大半才想起来还没翻上几页,在此先推荐给大家. 此书内容及其简洁干练,如果你对 ...
- Java 函数式编程(Lambda表达式)与Stream API
1 函数式编程 函数式编程(Functional Programming)是编程范式的一种.最常见的编程范式是命令式编程(Impera Programming),比如面向过程.面向对象编程都属于命令式 ...
- Java 函数式编程和Lambda表达式
1.Java 8最重要的新特性 Lambda表达式.接口改进(默认方法)和批数据处理. 2.函数式编程 本质上来说,编程关注两个维度:数据和数据上的操作. 面向对象的编程泛型强调让操作围绕数据,这样可 ...
- Lambda&Java多核编程-5-函数式接口与function包
从前面的总结中我们知道Lambda的使用场景是实现一个函数式接口,那么本篇就将阐述一下何为函数式接口以及Java的function包中提供的几种函数原型. 函数式接口 早期也叫作SAM(Single ...
- Lambda&Java多核编程-6-方法与构造器引用
在Lambda&Java多核编程-2-并行与组合行为一文中,我们对Stream<Contact>里的每一位联系人调用call()方法,并根据能否打通的返回结果过滤掉已经失效的项. ...
- Java函数式编程和lambda表达式
为什么要使用函数式编程 函数式编程更多时候是一种编程的思维方式,是种方法论.函数式与命令式编程的区别主要在于:函数式编程是告诉代码你要做什么,而命令式编程则是告诉代码要怎么做.说白了,函数式编程是基于 ...
随机推荐
- Myeclipse SVN 修改用户名和密码
转自:http://blog.csdn.net/chow__zh/article/details/7731497 解决方案: 在Eclipse使用SVN的过程中大多数人往往习惯把访问SVN的用户名密码 ...
- UVa 10716 - Evil Straw Warts Live
题目大意:给一个字符串,判断是否能通过交换字母构成回文,如果能,计算所需的最小交换次数. 如果字符串中出现奇数次的字母的个数>1,则不能构成回文.然后...就没思路了...看网上说用贪心的思想先 ...
- db2 将原表列notnull属性修改为null属性的方法 (查看主键约束,唯一约束去syscat.tabconst)
好久没机会写点东西了,今天把自己遇到的一个小问题跟大家分享一下如何修改db2数据库表中列的属性--将列的非空属性改为允许空的属性,修改数据表的某一列属性其实很简单但是里面有需要细节需要dba注意,毕竟 ...
- PHP获取图片颜色值
PHP获取图片颜色值,检测图片主要颜色的代码: <?php $i=imagecreatefromjpeg("photo3.jpg");//测试图片,自己定义一个,注意路径 f ...
- JDBC oracle 错误总结
ORA-28040: No matching authentication protocol jdk:1.8 oracle:12c 使用ojdbc14.jar 报错:ORA-28040: No mat ...
- 一张图告诉你什么是Linux distributions...
- 使用(Drawable)资源———ShapeDrawable资源
ShapeDrawable用于定义一个基本的几何图形(如矩形.圆形.线条等),定义ShapeDrawable的XML文件的根元素是<shape.../>元素,该元素可指定如下属性. and ...
- jQuery 鼠标滚轮插件 jquery.mousewheel.js
jQuery Mousewheel Plugin,用于添加跨浏览器的鼠标滚轮支持.mousewheel事件的处理函数有一点小小的变化,它除了第一个参数event 外,还接收到第二个参数delta.通过 ...
- 建立、配置和使用Activity——启动、关闭Activity
一个Android应用通常都会包含多个Activity,但只有一个Activity会作为程序的入口——当该Android应用运行时将会自启动并执行该Activity.至于应用中的其他Activity, ...
- java Swing 图片缓冲机制
java Swing 图片缓冲机制: 参考:http://jorneyr.iteye.com/blog/868858#comments package util; import java.awt.ge ...