lambda捕获this

大约一个月前,我在Java 8的lambda表达式框架下总结了Brian Goetz的观点 。 目前,我正在研究有关默认方法的文章,令我惊讶的是,我又回到了Java处理lambda表达式的方式。 这两个功能的交集可能会产生微妙但令人惊讶的效果,我想讨论一下。

总览

为了使这一点更有趣,我将以一个示例开头,该示例将以我的个人WTF达到顶峰 时刻。 完整的示例可以在专用的GitHub项目中找到 。

然后,我们将看到有关此意外行为的解释,并最终得出一些结论以防止错误。

这里有个例子……它不是那么简单或抽象,因为我希望它显示这种情况的相关性。 但是从某种意义上说,这仍然是一个示例,它仅暗示实际上可能有用的代码。

功能界面

假设对于在构建过程中已经存在结果的场景,我们需要对Future接口进行特殊化。

我们决定通过创建一个接口ImmediateFuture来实现此目的,该接口get()使用默认方法实现除get()之外的所有功能。 这导致功能界面 。

您可以在此处查看源代码。

一个工厂

接下来,我们实现FutureFactory 。 它可能会创建各种期货,但肯定会创建我们的新子类型。 它是这样的:

未来工厂

/*** Creates a new future with the default result.*/
public static Future<Integer> createWithDefaultResult() {ImmediateFuture<Integer> immediateFuture = () -> 0;return immediateFuture;
}/*** Creates a new future with the specified result.*/
public static Future<Integer> createWithResult(Integer result) {ImmediateFuture<Integer> immediateFuture = () -> result;return immediateFuture;
}

创造未来

最后,我们使用工厂创建一些期货并将其收集在一组中:

创建实例

public static void main(String[] args) {Set<Future<?>> futures = new HashSet<>();futures.add(FutureFactory.createWithDefaultResult());futures.add(FutureFactory.createWithDefaultResult());futures.add(FutureFactory.createWithResult(42));futures.add(FutureFactory.createWithResult(63));System.out.println(futures.size());
}

WTF ?!

运行程序。 控制台会说...

4? 不。 3。

WTF ?!

Lambda表达式的评估

那么这是怎么回事? 那么,与有关lambda表达式的评估一些背景知识,这其实并不奇怪。 如果您不太熟悉Java的实现方式,那么现在是赶上Java的好时机。 做到这一点的一种方法是观看Brian Goetz的演讲“ Java中的Lambdas:深入了解”或阅读我的摘要 。

Lambda表达式的实例

理解这种行为的关键在于,事实是JRE不保证如何将lambda表达式转换为相应接口的实例。 让我们看一下Java语言规范对此事的看法:

15.27.4。 Lambda表达式的运行时评估

[…]

分配并初始化具有以下属性的类的新实例,或者引用具有以下属性的类的现有实例。

[…类的属性–这里不足为奇…]

这些规则旨在通过以下方式为Java编程语言的实现提供灵活性:

  • 不必在每次评估中分配一个新对象。
  • 由不同的lambda表达式产生的对象不必属于不同的类(例如,如果主体相同)。
  • 评估产生的每个对象不必属于同一类(例如,可以内联捕获的局部变量)。
  • 如果“现有实例”可用,则不需要在先前的lambda评估中创建它(例如,可能在封闭类的初始化期间分配了它)。

[…]

JLS,Java SE 8版,§15.27.4

在其他优化中,这显然使JRE可以返回相同的实例,以重复评估lambda表达式。

非捕获Lambda表达式的实例

请注意,在上面的示例中,表达式不捕获任何变量。 因此,它永远不会因评估而改变。 而且由于lambda并非设计为具有状态,因此不同的评估在其生命周期中也无法“分散”。 因此,一般而言,没有充分的理由来创建多个不捕获的lambda实例,因为它们在整个生命周期中都完全相同。 这样可以使优化始终返回相同的实例。

(将其与捕获某些变量的lambda表达式进行对比。对此表达式的直接评估是创建一个将捕获的变量作为字段的类。每个评估都必须创建一个新实例,将捕获的变量存储在其字段中这些情况显然并不普遍。)

这就是上面代码中发生的事情。 () -> 0是一个不捕获的lambda表达式,因此每个评估都返回相同的实例。 因此,对createWithDefaultResult()每次调用都是如此。

但是,请记住,这仅适用于当前安装在我的计算机上的JRE版本(用于Win 64的Oracle 1.8.0_25-b18)。 您的可以有所不同,下一个gal也可以如此等等。

得到教训

因此,我们了解了为什么会这样。 尽管这很有意义,但我仍然会说这种行为并不明显,因此并不是每个开发人员都期望的。 这是漏洞的滋生地,因此让我们尝试分析情况并从中学习一些东西。

使用默认方法进行子类型化

可以说,意外行为的根本原因是如何完善Future的决定。 为此,我们扩展了另一个接口,并使用默认方法实现了部分功能。 仅剩一个未实现的方法, ImmediateFuture成为了一个启用lambda表达式的功能接口。

另外, ImmediateFuture可以是抽象类。 这样可以防止工厂意外返回相同的实例,因为它不能使用lambda表达式。

关于抽象类和默认方法的讨论不容易解决,因此在这里我不尝试这样做。 但是,我很快将发布有关默认方法的文章,并且我打算再讲一遍。 可以说,在做出决定时应考虑此处提出的案例。

工厂中的Lambda

由于lambda的引用相等性不可预测,因此工厂方法应仔细考虑使用它们来创建实例。 除非方法的协定明确允许不同的调用返回相同的实例,否则应完全避免使用它们。

我建议在此禁令中包括捕获lambda。 (对我而言)一点也不清楚,在什么情况下可以在将来的JRE版本中重用同一实例。 一种可能的情况是,JIT发现紧密的循环创建了总是(或至少经常)返回同一实例的供应商。 通过用于不捕获lambda的逻辑,重用同一供应商实例将是有效的优化。

匿名类与Lambda表达式

注意匿名类和lambda表达式的不同语义。 前者保证创建新实例,而后者则不能。 为了继续该示例,以下createWithDefaultResult()将导致futures –大小为4的集合:

匿名类的替代实现

public static Future<Integer> createWithDefaultResult() {ImmediateFuture<Integer> immediateFuture = new ImmediateFuture<Integer>() {@Overridepublic Integer get() throws InterruptedException, ExecutionException {return 0;}};return immediateFuture;
}

这尤其令人不安,因为许多IDE允许从匿名接口实现到lambda表达式的自动转换,反之亦然。 由于两者之间存在细微的差异,这种看似纯粹的句法转换会带来细微的行为变化。 (我最初并不了解。)

万一您遇到了这种情况,并选择使用匿名类,请确保以明显方式记录您的决定! 不幸的是,似乎没有办法阻止Eclipse对其进行任何转换(例如,如果将转换作为保存操作启用),这也会删除匿名类中的所有注释。

最终的替代方案似乎是一个(静态)嵌套类。 我知道没有哪个IDE敢将其转换为lambda表达式,因此这是最安全的方法。 尽管如此,仍需要对其进行文档记录,以防止下一个Java-8迷(确实像您一样)出现并加紧您的仔细考虑。

功能接口标识

当您依赖功能接口的标识时要小心。 始终考虑是否有可能,无论您在何处获得这些实例,都可能反复将您交给同一个实例。

但这当然是模糊的,几乎没有什么具体的结果。 首先,所有其他接口都可以简化为功能接口。 这实际上就是我选择Future的原因-我想举一个不会立即尖叫疯狂的LAMBDA狗屎的例子 其次,这会使您很快变得偏执。

因此,请不要过分考虑-记住这一点。

保证行为

最后但并非最不重要的一点(这始终是正确的,但值得在此重复):

不要依靠无证的行为!

JLS不保证每个lambda评估都返回一个新实例(如上面的代码所示)。 但是它不能保证观察到的行为,即非捕获的lambda总是由同一实例表示。 因此,不要编写依赖于两者的代码。

不过,我必须承认,这是一个艰难的过程。 认真地说,谁在使用某些功能之前先看过它们的JLS? 我当然不会。

反射

我们已经看到Java不能保证所评估的lambda表达式的身份。 尽管这是一个有效的优化,但它可能会产生令人惊讶的效果。 为了防止这种情况引入细微的错误,我们导出了以下准则:

  • 使用默认方法部分实现接口时要小心。
  • 不要在工厂方法中使用lambda表达式。
  • 当身份很重要时,请使用匿名类或更好的内部类。
  • 依赖功能接口的标识时要小心。
  • 最后, 不要依赖未记录的行为!

翻译自: https://www.javacodegeeks.com/2015/01/instances-of-non-capturing-lambdas.html

lambda捕获this

lambda捕获this_非捕获Lambda的实例相关推荐

  1. 非捕获Lambda的实例

    大约一个月前,我在Java 8的lambda表达式框架下总结了Brian Goetz的观点 . 目前,我正在研究有关默认方法的文章,令我惊讶的是,我又回到了Java处理lambda表达式的方式. 这两 ...

  2. 正则基础之——非捕获组

    非捕获组:(?:Expression) 接触正则表达式不久的人,通常都会对非捕获比较迷惑,为什么要有非捕获组?作用是什么?应该在什么场景下使用? 说到非捕获组,首先要了解什么是捕获组,详细内容参考 正 ...

  3. c++的lambda表达式捕获this_贯穿 C++ 11 与 C++ 17 的 Lambda 到底是个什么?

    本文将详解Lambda函数从定义到学习和使用,涉及一些不为人知的事情,如LIFE-立即调用的函数表达式,Lambda的类型.相信你已经起了兴趣,那就开始阅读吧. 作者 | Vishal Chovati ...

  4. java闭包lambda,(lambda)函数闭包捕获了什么?

    这是一个新的例子,它突出了闭包的数据结构和内容,以帮助澄清封闭上下文何时被"保存" . def make_funcs(): i = 42 my_str = "hi&quo ...

  5. 正则表达式 非捕获性分组

    非捕获性分组语法为(?:pattern),即将pattern部分组合成一个可统一操作的组合项,但不把这部分内容当作子匹配捕获,匹配的内容部进行编号也不存储在缓冲区中供以后使用.非捕获性分组方法在必须进 ...

  6. php 正则匹配分组命名,正则表达式、分组、子匹配(子模式)、非捕获子匹配(子模式)...

    前面我们知道正则表达式有很多元字符表示匹配次数(量词),都是可以重复匹配前面出现的单个字符次数.有时候,我们可能需要匹配一组多个字符一起出现的次数.这个时候,我们需要分组了.就是用小括号来括起这些字符 ...

  7. java正则表达式基础 关于特殊字符、捕获组和非捕获匹配

    JAVA正则表达式 我个人认为正则表达式是很好用很强大的,在编写程序中很多地方都用的到,这里有一些我学习的基础理解和大家分享,欢迎一起讨论. 正则表达式是一种用来表达语法规则的字符串,是一种字符串匹配 ...

  8. 正则表达式中的非捕获组是什么?

    非捕获组(即(?:) )如何在正则表达式中使用,它们有什么用? #1楼 在复杂的正则表达式中,您可能会希望使用大量的组,其中一些用于重复匹配,而另一些则提供反向引用. 默认情况下,与每个组匹配的文本会 ...

  9. 【正则表达式系列】一些概念(字符组、捕获组、非捕获组)

    前言 本文介绍一些正则中的常用名词以及对应概念,譬如字符组,捕获组.非捕获组.反向引用.转义和\s \b等 大纲 字符组 捕获组 反向引用 非捕获组 ..\s和\S \b \转义 字符组 []字符组表 ...

最新文章

  1. mysql5.6 mac10.11_Mac Pro 解压安装MySQL二进制分发版 mysql-5.6.30-osx10.11-x86_64.tar.gz(不是dmg的)...
  2. Android9.0 新特性
  3. lnmp一键安装调优,lnmp,memcache,全文检索
  4. 分析mysql日志文件_MySQL日志文件与分析
  5. 创建存储,修改存储_安全地创建和存储密码
  6. mysql5.7卸载语句_MySQL5.7完全卸载
  7. PHP代码猜数字游戏,js实现一个猜数字游戏
  8. 阿里云物联网平台 > 设备接入 > 使用开放协议自主接入 > MQTT协议接入 >
  9. 计算机算法设计与分析 Huffman编码问题
  10. HTML不刷新,改数据
  11. Silverlight+WCF 新手实例 象棋 棋子移动-规则补充(三十七)
  12. 声学计算机软件,常用声学仿真软件汇总
  13. [picture_scrapy] 关于美女爬虫的一个集合
  14. Java中swing修改左上角的图标
  15. 双重差分模型能做固定效应吗_双重差分法的平行趋势假定
  16. Gateway—网关服务
  17. JS的onBlur事件与onfocus事件
  18. python中排序从小到大_python怎么从小到大排列
  19. 编程小题目之01:从键盘输入一数字a,求S=a+aa+aaa+aaaa+aa...a的值,例如:a=2,S=2+22; a=4,S=4+44+444+4444
  20. jdk 8.0报ignoring option PermSize MaxPermSize解决方法

热门文章

  1. POJ2481-Cows【树状数组】
  2. 【线段树】Optimal Insertion(CF751E)
  3. 【DP】Mobile Service(jzoj 1327)
  4. 1D/1D动态规划的三种优化方法
  5. [XSY] 绿色(圆方树、树形DP、树上差分)
  6. 操作系统复习笔记 05 Thread 线程
  7. 2017西安交大ACM小学期 神器插座 KMP匹配
  8. 7、mybatis中的sql映射文件详解(2)
  9. 4、oracle数据库的查询基础
  10. JS 获取浏览器、显示器 窗体等宽度和高度