跳到主要内容

JEP 109:使用 Lambda 增强核心库

概括

使用新的 lambda 语言功能增强 Java 核心库 API,以提高库的可用性和便利性。

目标

主要目标是通过在合适的位置添加 Lambda 的使用来实现通用库 API 的现代化。大多数实现将作为现有类的扩展方法提供。我们将瞄准图书馆的高流量区域,并在我们认为最有好处的地方添加 Lambda API。理想情况下,Lambda 将出现在熟悉 Lambda 的程序员期望的 API 中的任何位置。或者,我们希望主流程序员在库中偶然发现 Lambda API 时会想:“哦,太酷了,他们在这里添加了 Lambda,这让我可以更轻松地解决我的问题。”

第二个目标是通过在库 API 中使用 Lambda、从真实代码中调用它们、评估结果并向 Lambda 语言/编译器团队提供反馈来为 Lambda 语言功能的设计提供信息。

目标可概括如下:

  • 向现有库引入新的惯用法,即 lambda 函数;
  • 使用 lambda 函数提高库的实用性和便利性;
  • 展示扩展方法的最佳实践;和
  • 展示熟悉的核心库的创新和演变。

非目标

我们的目标并不是在所有可能使用 Lambda 的地方使用 Lambda,范围也不会扩展到核心库之外。例如,客户端、XML 和 CORBA 不属于此工作范围。

通过此增强功能,核心类中将添加很少的新功能——只是执行熟悉任务的新方法。

对于升级为使用 Lambda 的 API 比例,没有具体的目标,即“我们将把 Lambda 添加到库的 xx%”之类的内容。

成功指标

成功与否将根据开发人员采用新 API 和功能的程度来评估。为了获得完全成功,Lambda 功能的使用将成为 Java 开发人员使用核心库的默认习惯用法。

动机

“语言设计就是图书馆设计。
图书馆设计就是语言设计。” ——安德鲁·科尼格

Java 8 将包含一项名为 Lambda 的新语言功能。在语言中拥有此功能很有用,但仅对 Lambda 进行语言更改,该平台是不完整的。将 Lambda 支持添加到库 API 的适当区域将使该平台变得更有价值。

在过去的几年里,各种新的编程语言已经出现并越来越受欢迎。大多数这些语言都有某种类型的块、闭包或一等函数构造。虽然 Java 仍然是排名第一的编程语言,但普遍的观点是它没有跟上编程语言的最新发展。这被公认为 Lambda 语言功能本身的动机。但是,还需要考虑支持使用 Lambda 的库 API。所有替代语言都有库,并且它们的 API 都经过调整,可以与该语言提供的闭包或函数对象一起顺利且惯用地工作。

同样,对于 Java,我们预计随着时间的推移,Lambda 的使用将变得广泛,并且围绕此功能将开发出各种编码习惯。 Java 库 API 需要得到增强,以支持 Lambda 的惯用用法以及当前的常规用法。

描述

该项目分为两个阶段:

  1. 调查阶段,以确定 Lambda API 增强功能的候选者;和

  2. 范围界定和实施阶段,用于优先考虑并选择候选者的子集并实施它们。

候选人调查

有多种方法可以发现添加基于 Lambda 的 API 的候选站点。一种方法是检查具有类似语言功能的其他系统,并查看它们的库并了解它们如何使用闭包和函数。

以 Ruby 为例。 Ruby 语言支持各种一流的类似函数的构造。它们本身很有用,因为它们使程序员能够使用函数式编程、创建高阶函数、组合函数等。此外,由于 Ruby 类库是与支持优先级的语言一起开发的-类函数,类库中的许多API都使用它们。我们可以浏览Ruby 类库来查找示例,我们可以将这些示例用作 Java 库潜在增强功能的灵感。 Ruby 类库中的一些示例如下:

  • Integer 类具有_times_、upto、_downto_和_step_方法,支持各种算术迭代结构。这消除了在语言中定义各种此类迭代结构的需要。

  • File 类有一个采用块的_open_方法的变体。文件被打开,传递给块,并在块返回时关闭。这有助于避免资源泄漏,并且减少了语言中特殊的执行结构(例如 Java 7 的 try-with-resources 结构)的需要。

  • HTTP API 类有多种需要块的方法。例如,HTTPResponse 类有一个_each_方法,该方法调用一个采用(标头、值)对的块。

  • 在 Ruby 的 Tk 绑定中,小部件创建方法_new_需要一个在新创建的小部件上下文中执行的块,这提供了一种方便而简洁的初始化小部件的方法。

很明显,在集合类之外有很多使用 Lambda 的机会。不需要像 Ruby 那样在所有地方都实现基于 Lambda 的 API,但考虑到 Ruby 的受欢迎程度和关注度,从 Ruby 的类库中寻找初步想法似乎是合理的。检查其他系统(例如 Groovy、Scala、Python、Clojure 和 Smalltalk)也可能是合理的,以找到在库 API 中使用 Lambda 的类似灵感。

另一种方法是查看现有的 Java 代码(包括库内和外部,例如来自 Qualitas Corpus),并进行模式匹配或综合来发现候选代码。一组技术可能如下:

  • 运行 API 并将_each()_或_forEach()_方法添加到任何可能包含某些内容的集合或可以迭代的内容,即使它不是集合。例如,可以将_forEachLine()_方法添加到 java.nio.file.Files 中,该方法为该文件中的每一行调用 Lambda。

  • 寻找机会使用“execute-around”习惯用法。例如,文件使用后必须关闭、锁使用后必须解锁等。

  • 寻找实现 Iterable 的类,并考虑 Lambda 是否有助于反转控制流。

  • 在代码中查找 for 循环和 while 循环,并考虑使用 Lambda 来反转控制流。

  • 识别具有一两个抽象方法的抽象类,并考虑添加采用 lambda 表达式的构造函数或工厂。对于接口也要考虑这一点。该模式是通过重写“填充”实现的地方;考虑如何将其转换为传递 Lambda 作为参数。

范围界定和实施

由此产生的候选集可能太长,无法在 JDK 8 时间范围内实现。需要对它们进行优先级排序,然后进行截断以适应可用的时间表,并为项目分配一组给定的资源(人员)。优先级可以根据库的主观重要性来确定,但最好有一些使用数据来支持它。例如,对 Qualitas 语料库进行调查以确定图书馆的哪些区域使用最频繁可能会很有用。

语言设计问题

使用Lambda进行API开发时,需要考虑以下问题,并将相关信息反馈给Lambda语言设计团队。

  1. 异常透明度:您是否发现需要编写可以抛出已检查异常的 lambda 表达式?您是否需要额外的具有“抛出”子句的 SAM 类型集,也许是通用的“抛出 E”?结果是不是极其乏味?

  2. 差异:函数式接口的实用方法可能会比 API 中的其他任何内容更多地使用通配符;这是不是非常乏味?拥有更好/更少详细的方差支持会有用吗?

  3. 未装箱覆盖:在功能接口的设计中允许原始/无效返回类型覆盖引用返回类型(例如)是否有用Predicate<T> <: Function<T, Boolean>

  4. 抽象类函数式接口:在上一篇关于抽象类的文章中,是否有这么多有用的候选者,直接支持抽象类作为函数式接口,而不是手动定义子类会很好吗?

  5. 通用方法函数式接口:您是否发现拥有一个具有通用方法的函数式接口很有用,例如:interface MapFactory { <K,V> Map<K,V> make(); }。请注意,这里的重点是_方法_是通用的,而不是_接口_。目前不支持此功能;好处是每次调用都可以推断出新的类型参数,这在某些应用程序中可能很有用。

  6. 链接推理:在“foo().bar(23)”等表达式中,“foo”的类型参数是独立于表达式的“bar(23)”部分推断的。我们计划在许多情况下改进上下文在推理中的使用,但我们仍在寻找一些经验来证明到目前为止的做法是合理的。

  7. 方法引用消歧:您是否发现需要通过编写完整签名或放弃并使用 lambda 表达式来显式消除方法引用的歧义(当方法重载或存在静态/实例冲突时)?

测试

需要为添加到系统中的每个新 API 开发测试。新的 API 在很大程度上彼此独立,因此单独测试它们应该很简单。此外,API 增强可能主要包括添加不会影响同一类中其他 API 的新方法。因此,这些新 API 的测试应该相当简单,并且不会影响使用当前 API 的现有测试。

风险和假设

添加额外的编程习惯会增加新用户的复杂性。如果 Lambda API 是在整个系统中零碎添加的,那么它们的风格可能会有所不同。首先进行调查并制定适用于整个图书馆的一致风格可能会有所帮助。

降低风险的一点是,由于 API 增强功能在很大程度上彼此独立,因此可以更改范围而不会对项目产生太大影响。也就是说,将范围视为 API 增强功能的优先级列表。根据项目进度和人员配置,将在某个点划一条线。如果这条生产线因任何原因需要移动,这应该不会对已经完成的工作产生影响,并且几乎不需要重新规划,因为这些项目在很大程度上是相互独立的。出于同样的原因,如果在项目进行期间有新信息可用,也应该可以不费太多力气重新排列列表的优先顺序。

将这些新 API 与现有 API 混合使用可能会使用户代码库的维护变得更加复杂。

依赖关系

这项工作仅取决于实际的 lambda 实现。

即使这是一个相当软的依赖关系,因为 lambda 在 API 中表示为 SAM。即使在集成 lambda 之前,也可以添加基于功能接口的 API,尽管调用者此时无法使用 lambda。

影响

  • JCP:这些都是对公共API的更改,因此他们最终将修改JCP控制的规范。对现有核心库类的这些更改应该足够小,不需要它们自己的 JSR;它们应该包含在 JSR 平台中。所需的新类(功能接口)将由 JSR 335 EG 设计和审查。当然,必须遵循适当的审查流程。

  • 其他 JDK 组件:Lambda 实现

  • 兼容性:API 更改不太可能导致不兼容性。

  • 文档:可能需要一些新的教程来展示如何将 Lambda 与新的库结构一起使用,或者提供一系列说明常见用法的小示例。在查看 javadoc 时,Lambda 可能很难辨别,因此使用一些新的 javadoc 语法或可能不同的 javadoc 输出格式来以某种独特的方式突出显示使用 lambda 的 API 可能是有用或必要的。另一种可能性是确保每个新的 Lambda 接受 API 的 javadoc 都包含其使用示例。