lambda捕获this_非捕获Lambda的实例
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的实例相关推荐
- 非捕获Lambda的实例
大约一个月前,我在Java 8的lambda表达式框架下总结了Brian Goetz的观点 . 目前,我正在研究有关默认方法的文章,令我惊讶的是,我又回到了Java处理lambda表达式的方式. 这两 ...
- 正则基础之——非捕获组
非捕获组:(?:Expression) 接触正则表达式不久的人,通常都会对非捕获比较迷惑,为什么要有非捕获组?作用是什么?应该在什么场景下使用? 说到非捕获组,首先要了解什么是捕获组,详细内容参考 正 ...
- c++的lambda表达式捕获this_贯穿 C++ 11 与 C++ 17 的 Lambda 到底是个什么?
本文将详解Lambda函数从定义到学习和使用,涉及一些不为人知的事情,如LIFE-立即调用的函数表达式,Lambda的类型.相信你已经起了兴趣,那就开始阅读吧. 作者 | Vishal Chovati ...
- java闭包lambda,(lambda)函数闭包捕获了什么?
这是一个新的例子,它突出了闭包的数据结构和内容,以帮助澄清封闭上下文何时被"保存" . def make_funcs(): i = 42 my_str = "hi&quo ...
- 正则表达式 非捕获性分组
非捕获性分组语法为(?:pattern),即将pattern部分组合成一个可统一操作的组合项,但不把这部分内容当作子匹配捕获,匹配的内容部进行编号也不存储在缓冲区中供以后使用.非捕获性分组方法在必须进 ...
- php 正则匹配分组命名,正则表达式、分组、子匹配(子模式)、非捕获子匹配(子模式)...
前面我们知道正则表达式有很多元字符表示匹配次数(量词),都是可以重复匹配前面出现的单个字符次数.有时候,我们可能需要匹配一组多个字符一起出现的次数.这个时候,我们需要分组了.就是用小括号来括起这些字符 ...
- java正则表达式基础 关于特殊字符、捕获组和非捕获匹配
JAVA正则表达式 我个人认为正则表达式是很好用很强大的,在编写程序中很多地方都用的到,这里有一些我学习的基础理解和大家分享,欢迎一起讨论. 正则表达式是一种用来表达语法规则的字符串,是一种字符串匹配 ...
- 正则表达式中的非捕获组是什么?
非捕获组(即(?:) )如何在正则表达式中使用,它们有什么用? #1楼 在复杂的正则表达式中,您可能会希望使用大量的组,其中一些用于重复匹配,而另一些则提供反向引用. 默认情况下,与每个组匹配的文本会 ...
- 【正则表达式系列】一些概念(字符组、捕获组、非捕获组)
前言 本文介绍一些正则中的常用名词以及对应概念,譬如字符组,捕获组.非捕获组.反向引用.转义和\s \b等 大纲 字符组 捕获组 反向引用 非捕获组 ..\s和\S \b \转义 字符组 []字符组表 ...
最新文章
- mysql5.6 mac10.11_Mac Pro 解压安装MySQL二进制分发版 mysql-5.6.30-osx10.11-x86_64.tar.gz(不是dmg的)...
- Android9.0 新特性
- lnmp一键安装调优,lnmp,memcache,全文检索
- 分析mysql日志文件_MySQL日志文件与分析
- 创建存储,修改存储_安全地创建和存储密码
- mysql5.7卸载语句_MySQL5.7完全卸载
- PHP代码猜数字游戏,js实现一个猜数字游戏
- 阿里云物联网平台 > 设备接入 > 使用开放协议自主接入 > MQTT协议接入 >
- 计算机算法设计与分析 Huffman编码问题
- HTML不刷新,改数据
- Silverlight+WCF 新手实例 象棋 棋子移动-规则补充(三十七)
- 声学计算机软件,常用声学仿真软件汇总
- [picture_scrapy] 关于美女爬虫的一个集合
- Java中swing修改左上角的图标
- 双重差分模型能做固定效应吗_双重差分法的平行趋势假定
- Gateway—网关服务
- JS的onBlur事件与onfocus事件
- python中排序从小到大_python怎么从小到大排列
- 编程小题目之01:从键盘输入一数字a,求S=a+aa+aaa+aaaa+aa...a的值,例如:a=2,S=2+22; a=4,S=4+44+444+4444
- jdk 8.0报ignoring option PermSize MaxPermSize解决方法