Java 中惰性求值的潜能,完全被忽视了(在语言层面上,它仅被用来实现 短路求值 )。更先进的语言,如 Scala,区分了传值调用与传名调用,或者引入了 lazy 这样的关键字。

尽管 Java 8 通过延迟队列的实现(java.util.stream.Stream)在惰性求值的方面有些改进,但是我们会先跳过 Stream,而把重点放在如何使用 lambda 表达式实现一个轻量级的惰性求值。

基于 lambda 的惰性求值

Scala

当我们想对 Scala 中的方法参数进行惰性求值时,我们用“传名调用”来实现。

让我们创建一个简单的 foo 方法,它接受一个 String 示例,然后返回这个 String:

deffoo(b:String):String=b

一切都是马上返回的,跟 Java 中的一样。如果我们想让 b 的计算延迟,可以使用传名调用的语法,只要在 b 的类型声明上加两个符号,来看:

deffoo(b:=> String):String=b

如果用 javap 反编译上面生成的 *.class 文件,可以看到:

Compiled from "LazyFoo.scala"

publicfinalclassLazyFoo {

publicstaticjava.lang.String foo(scala.Function0);

Code:

0: getstatic #17 // Field LazyFoo.MODULE:LLazyFoo$;

3: aload_0

4: invokevirtual #19 // Method LazyFoo$.foo:(Lscala/Function0;)Ljava/lang/String;

7: areturn

}

看起来传给这个函数的参数不再是一个String了,而是变成了一个Function0,这使得对这个表达式进行延迟计算变得可能 —— 只要我们不去调用他,计算就不会被触发。Scala 中的惰性求值就是这么简单。

使用 Java

现在,如果我们需要延迟触发一个返回T的计算,我们可以复用上面的思路,将计算包装为一个返回Supplier实例的 Java Function0 :

Integer v1 = 42; // eager

Supplier v2 = () -> 42; // lazy

如果需要花费较长时间才能从函数中获得结果,上面这个方法会更加实用:

Integer v1 = compute(); //eager

Supplier value = () -> compute(); // lazy

同样的,这次传入一个方法作为参数:

privatestaticintcomputeLazily(Supplier value) {

// ...

}

如果仔细观察 Java 8 中新增的 API,你会注意到这种模式使用得特别频繁。一个最显著的例子就是 Optional#orElseGet ,Optional#orElse 的惰性求值版本。

如果不使用这种模式的话,那么 Optional 就没什么用处了… 或许吧。当然,我们不会满足于 suppliers 。我们可以用同样的方法复用所有 functional 接口。

线程安全和缓存

不幸的是,上面这个简单的方法是有缺陷的:每次调用都会触发一次计算。不仅多线程的调用有这个缺陷,同一个线程连续调用多次也有这个缺陷。不过,如果我们清楚这个缺陷,并且合理的使用这个技术,那就没什么问题。

使用缓存的惰性求值

刚才已经提到,基于 lambda 表达式的方法在一些情况下是有缺陷的,因为返回值没有保存起来。为了修复这个缺陷,我们需要构造一个专用的工具,让我们叫它 Lazy :

publicclassLazy { ... }

这个工具需要自身同时保存Supplier和 返回值T。

@RequiredArgsConstructor

publicclassNaiveLazy {

privatefinalSupplier supplier;

privateT value;

publicT get() {

if(value ==null) {

value = supplier.get();

}

returnvalue;

}

}

就是这么简单。注意上面的代码仅仅是一个概念模型,暂时还不是线程安全的。

幸运的是,如果想让它变得线程安全,只需要保证不同的线程在获取返回值的时候不会触发同样的计算。这可以简单的通过双重检查锁定机制来实现(我们不能直接在 get() 方法上加锁,这会引入不必要的竞争):

@RequiredArgsConstructor

publicclassLazy {

privatefinalSupplier supplier;

privatevolatileT value;

publicT get() {

if(value ==null) {

synchronized(this) {

if(value ==null) {

value = supplier.get();

}

}

}

returnvalue;

}

}

现在,我们有了一个完整的 Java 惰性求值的函数化实现。由于它不是在语言的层面实现的,需要付出创建一个新对象的代价。

更深入的讨论

当然,我们不会就此打住,我们可以进一步的优化这个工具。比如,通过引入一个惰性的filter()/flatMap()/map()方法,可以让它使用起来更加流畅,并且组合性更强:

public Lazy map(Function mapper) {

returnnewLazy<>(() -> mapper.apply(this.get()));

}

public Lazy flatMap(Function> mapper) {

returnnewLazy<>(() -> mapper.apply(this.get()).get());

}

作者:Java大生

链接:https://www.jianshu.com/p/fe009908f32c

java惰性计算原理_利用 Lambda 表达式实现 Java 中的惰性求值相关推荐

  1. java标签多行_利用Arcade表达式显示多行标签

    要素图层依然是全球气象站点分布数据,属性表如下: 展示效果如下,显示的四行数据分别是属性表中的WIND_NAME,TEMP,WIND(运算之后的),R_HUMIDITY 本次尝试一次性写全所有的信息: ...

  2. lambda 对象去重_采用java8 lambda表达式 实现 java list 交集 并集 差集 去重复并集...

    采用java8 lambda表达式 实现java list 交集/并集/差集/去重并集 一般的javaList 交.并集采用简单的 removeAll retainAll 等操作,不过这也破坏了原始的 ...

  3. 【Java】反射、枚举、Lambda表达式

    一.反射 1 定义 2 用途(了解) 3 反射基本信息 4 反射相关的类(重要) 4.1 Class类(反射机制的起源 ) 4.1.1 Class类中的相关方法(方法的使用方法在后边的示例当中) 4. ...

  4. 4.File类、Lambda表达式、JAVA IO

    一.File类 File类的每一个实例可以表示硬盘(文件系统)中的一个文件或目录(实际上表示的是一个抽象路径),使用File可以做到: 1:访问其表示的文件或目录的属性信息,例如:名字,大小,修改时间 ...

  5. 0202年了,还没有用上Java函数式编程!!!——Lambda表达式

    0202年了,还没有用上Java函数式编程!!!--Lambda表达式 函数式编程是什么 命令式编程(Imperative) 声明式编程(Declarative) 函数式编程(Functional) ...

  6. Java 8:一文掌握 Lambda 表达式 | CSDN 博文精选

    作者 | Android 大强哥 责编 | 郭芮 出品 | CSDN 博客 本文将介绍 Java 8 新增的 Lambda 表达式,包括 Lambda 表达式的常见用法以及方法引用的用法,并对 Lam ...

  7. 利用Lambda表达式解决c#文件名排序问题(是100大还是11大的问题)

    1.问题背景: 有时需要对输出的文件列表进行排序,如直接使用List<T>.Sort()方法排序时,遇到9, 10,11和100排序的问题,往彺会出现2.xxx大于100.xxx (其中, ...

  8. java 增量编译_java增量构建关于lambda表达式的问题

    项目需要做java增量构建和部署.基本方案很简单. 构建:通过git diff拿到方法体变化的java类,绕过maven,直接使用同版本的javac编译出class文件. 部署:基于java-inst ...

  9. 匿名内部类以及Lambda表达式(Java和C#)

    就来说一说这个匿名内部类,用Java做例子,后面插一点c#.这个东西我之前也知道了很久,一直没有认真去看过,这次就来看看吧,今天的代码量相对很多. 匿名内部类 这里就说匿名内部类,关于其他内部类老规矩 ...

最新文章

  1. 百度重磅发布云手机:低配置也可玩大型游戏 21
  2. java 内部类泛型,java – 使用泛型强制转换为内部类
  3. python去噪算法
  4. Spring 中获取servletContext及WebApplicationContext以及applicationContext三者之间的关系
  5. Github项目|几行代码即可实现人脸检测、目标检测的开源计算机视觉库
  6. 单片机can通信可以接多少个设备_总结BMS上CAN收发器电路的几个要点
  7. (day 07 - dfs or bfs)剑指 Offer 13. 机器人的运动范围
  8. 页面PDF预览控件pdf.js使用总结
  9. PDP context激活的大致原理
  10. Cheat Engine游戏脚本修改器通关教程(脑残版Step1-Step8)
  11. 《哪咤学python进阶篇》之选学案例三:白桦林的故事_(Python多媒体MV)
  12. 转x电容和y电容的区别
  13. 老庙黄金2016春晚抢红包活动技术架构详解
  14. free掉结点一定会造成断链吗?
  15. 显存测试软件linux环境_CI/CD 中的自动化测试的概要知识 | Linux 中国
  16. 华为line服务器无响应,line注册链接不到服务器
  17. unexpected char: '\' @ line 3, column 133. s\react-native-gestu
  18. js通过身份证号获取出生日期,性别,年龄
  19. 苹果电脑IOS ssh远程关机
  20. 解决msn登陆不了的方法

热门文章

  1. 基因大数据在生活中的应用
  2. win10安装python详细过程
  3. Nuendo学习之旅——音程和和弦
  4. 【这很AI】是福是祸?科学家利用AI翻译狗的语言,人狗对话将成可能
  5. MAC突然连不上蓝牙
  6. JS--页面加载完毕后执行
  7. 监听audio是否加载完毕
  8. Linux安装aria2,并使用AriaNg图形管理
  9. 如何1秒内快速判断一个函数的凹凸性?还看不懂我给你赔钱
  10. Arithmetic(线段树维护历史版本和)