这周开学,上了两天感觉课好多,学校现在还停水,宿舍网络也还没通,简直爆炸,感觉能静下心看书的时间越来越少了... 寒假还有些看过书之后的存货,现在写一点发出来。加上假期两个月左右都过去了书才看了 1/7 都不到... 还得去续借一下,看来买书多看书少的毛病也得改一改,先致力于把剁手买的书啃完。
另外再次推荐下我现在看的这本书(详见第 0 篇),越看越费劲... 干货非常多而且特别干,总之相比于其他书可以说是一页顶三页了,每一段都值得仔细琢磨,发现看不懂的还得调转方向先去填坑。
接上一篇:
当使用匿名内部类并去实现其中的接口时,更多时候我们不会去访问定义在外部的变量,反而更加倾向于将其写成类似于静态方法的一种 "函数"。
就如同前文中所举的键提取器、键比较器之类的例子,作为单纯的行为(如
类中的那些静态方法),不需要引入或操作任何外部量就能够达到目的。
- 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 与匿名内部类在访问外部变量时,都不允许有修改变量的倾向,即若:
- final double a = 3.141592;
- double b = 3.141592;
- DoubleUnaryOperator anotherDoubleUnaryOperator = x -> {
- a = 2; // ERROR
- b = 3; // ERROR
- return 0.0;
- };
则:
量的值
- 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。同时,我们若要解决线程安全问题,就需要给相关的外部变量上锁或使用
关键字,导致了计算任务分发至不同线程后的效率问题,又违背了 Lambda 的初衷。
- volatile
关于线程安全,书中还出现了以下两个关键词:
看来还需要提升知识水平才能把多线程、高并发的坑给填了,现在还不大能看懂(╯-_-)╯╧╧。
抛开线程不谈,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.
来源: http://www.cnblogs.com/hwding/p/6485751.html