JEP 109: Enhance Core Libraries with Lambda | 使用 Lambda 增强核心库
摘要
使用新的 lambda 语言特性增强 Java 核心库 API,以提高库的可用性和便利性。
目标
主要目标是通过在适当的位置添加 Lambda 的使用,来现代化通用库 API。大多数实现将作为现有类的扩展方法提供。我们将针对库的高流量区域,并在我们认为将获益最大的地方添加 Lambda API。理想情况下,Lambda 应该出现在 API 中,无论是程序员熟悉 Lambda 的地方。或者,我们希望主流程序员在库中遇到 Lambda API 时会想到:“哦,很酷,他们在这里添加了一个 Lambda,这让我更容易解决我的问题。”
次要目标是通过在库 API 中使用 Lambda 来影响 Lambda 语言特性的设计,从而调用实际代码中的库 API,评估结果,并向 Lambda 语言 / 编译器团队提供反馈。
目标可以总结如下:
- 在现有库中引入新的习惯用法,即 lambda 函数;
- 使用 lambda 函数改进库的实用性和方便性;
- 展示扩展方法的最佳实践;
- 展示熟悉的核心库的创新和演进。
非目标
非目标是在可以使用 Lambda 的每个可能的地方都使用 Lambda,范围也不会超出核心库。例如,不包括客户端、XML 和 CORBA 在内的部分不在本次努力范围内。
此增强不会向核心类添加太多新功能,只会提供使用熟悉任务的新方法。
没有特定的目标来确定使用 Lambda 的 API 比例,即没有类似“我们将向库添加 xx% 的 Lambda”的目标。
成功度量标准
成功将根据开发人员采用新 API 和功能的程度来评估。如果 Java 开发人员将使用 Lambda 功能作为使用核心库的默认方法,那么就可以完全成功。
动机
“语言设计就是库设计。库设计就是语言设计。” - Andrew R. Koenig
Java 8 将包含一个名为 Lambda 的新语言特性。拥有这个语言特性是有用的,但只有 Lambda 的语言更改,平台是不完整的。为了使平台更有价值,需要将 Lambda 支持添加到适当的库 API 区域。
在过去的几年里,出现了各种新的编程语言,并且越来越受欢迎。这些语言中的大多数都有某种块、闭包或一级函数构造。虽然 Java 仍然是 #1 编程语言,但普遍观点是它没有跟上编程语言的最新发展。这被广泛认为是 Lambda 语言特性本身的动机。然而,还需要考虑支持 Lambda 使用的库 API。其他替代语言都有库,它们的 API 被调整得很好 - 与语言提供的闭包或函数对象平滑和习惯化地配合工作。
同样,随着时间的推移,我们预计 Lambda 的使用将变得普遍,并且将出现各种围绕此功能的编码习惯。Java 库 API 将需要增强,以支持 Lambda 的习惯用法,同时支持当前的传统用法。
描述
这个项目有两个阶段:
- 一个调查阶段,用于确定 Lambda API 增强的候选对象;
- 一个规划和实施阶段,用于优先选择和确定候选对象的子集,并对它们进行实施。
候选对象调查
发现添加基于 Lambda 的 API 候选站点有几种方法。一种方法是检查具有类似语言特性的其他系统,并查看它们的库以了解它们如何使用闭包和函数。
以 Ruby 为例。Ruby 语言支持各种一等公民函数的构造。这些构造本身就很有用,因为它们使程序员能够使用函数式编程风格,创建高阶函数,组合函数等等。此外,由于 Ruby 类库是与支持一等公民函数的语言一起开发的,因此类库中的许多 API 都使用了它们。我们可以查看 Ruby 类库 以找到可以用作 Java 类库潜在增强的灵感来源。Ruby 类库中的一些例子包括:
Integer
类有 times、upto、downto 和 step 方法,支持各种算术迭代构造。这样就不需要在语言中定义各种这样的迭代构造了。File
类有一个接受 block 的 open 方法变体。文件在打开后传递给 block,并在 block 返回时关闭。这有助于避免资源泄漏,并减少了语言中特殊的执行环绕构造(如 Java 7 的 try-with-resources 构造)的需求。- HTTP API 类有几个接受 block 的方法。例如,
HTTPResponse
类有一个 each 方法,调用一个接受(header,value)对的 block。 - 在 Ruby 的 Tk 绑定中,小部件创建方法 new 接受一个 block,在新创建的小部件的上下文中执行该 block,这提供了一种方便而简洁的初始化小部件的方式。
很明显,在集合类之外使用 Lambda 的机会很多。并不要求在任何 Ruby 中实现基于 Lambda 的 API,但鉴于 Ruby 的受欢迎程度和影响力,从 Ruby 的类库中寻找初始的灵感似乎是合理的。也可以考虑查看其他系统,如 Groovy、Scala、Python、Clojure 和 Smalltalk,以寻找在库 API 中使用 Lambda 的类似灵感。
另一种方法是查看现有的 Java 代码 - 包括库内和外部的代码,例如来自 Qualitas Corpus - 并进行模式匹配或合成来发现候选对象。一组技术可能如下:
- 遍历 API,并为任何可能包含某种集合或可迭代对象的东西添加 each() 或 forEach() 方法,即使它不是一个集合。例如,可以在
java.nio.file.Files
中添加一个 forEachLine() 方法,该方法为该文件中的每一行调用一个 Lambda。 - 寻找使用执行环绕惯用法的机会。例如,文件在使用后必须关闭,锁在使用后必须解锁等等。
- 寻找实现了
Iterable
的类,并考虑是否使用 Lambda 来反转控制流是否有用。 - 查找代码中的 for 循环和 while 循环,并考虑使用 Lambda 来反转控制流。
- 确定具有一个或两个抽象方法的抽象类,并考虑添加接受 Lambda 的构造函数或工厂。对于接口也要考虑这一点。这种模式是通过重写来进行实现的,考虑如何将其转换为通过参数传递 Lambda 的方式。
规划和实施
最终的候选对象集可能太多,无法在 JDK 8 的时间范围内实施。它们需要进行优先排序,然后缩减以适应现有的计划,并分配给项目的一组资源(人员)。优先排序可以根据库的主观重要性进行,但最好有一些使用数据来支持。例如,可以对 Qualitas Corpus 进行调查,以确定库中使用最频繁的领域。
语言设计问题
在使用 Lambda 进行 API 开发时,应考虑以下问题,并将相关信息反馈给 Lambda 语言设计团队。
- 异常透明性:是否需要编写可能抛出已检查异常的 Lambda 表达式?是否需要具有带有 'throws' 子句的额外一组 SAM 类型,例如通用 'throws E' ?结果是否极其繁琐?
- 变异:函数式接口的实用方法可能比 API 中的其他任何内容都更频繁地使用通配符;这是否极其繁琐?是否有更好 / 更简洁的变异支持会很有用?
- 无装箱的重写:在函数式接口的设计中,允许原始 /
void
返回类型重写引用返回类型是否有用,例如Predicate<T> <: Function<T, Boolean>
? - 抽象类的函数式接口:在前面关于抽象类的项目中,是否有太多有用的候选者,以至于直接支持抽象类作为函数式接口会很好,而不必手动定义一个子类?
- 泛型方法的函数式接口:是否发现具有泛型方法的函数式接口很有用,例如:
interface MapFactory { <K,V> Map<K,V> make(); }
。请注意,这里的重点是方法是泛型的,而不是接口。目前不支持这种方式;好处是可以为每次调用推断出新的类型参数,这在某些应用程序中可能很有用。 - 链式推断:在表达式 'foo().bar(23)' 中,'foo' 的类型参数是独立于表达式的 'bar(23)' 部分进行推断的。我们计划改进推断中的上下文使用,但我们仍在寻找一些经验,以证明这种改进的必要性。
- 方法引用消歧义:是否需要显式消除方法引用的歧义(当方法重载或存在静态 / 实例冲突时),无论是通过编写完整的签名还是放弃并使用 Lambda 表 达式?
测试
需要为添加到系统中的每个新 API 开发测试。新的 API 在很大程度上是相互独立的,因此单独测试它们应该很简单。此外,API 增强可能主要包括添加不会影响同一类中其他 API 的新方法。因此,这些新 API 的测试应该相当简单,并且不应该影响使用当前 API 的现有测试。
风险和假设
添加额外的编程习惯用法会增加新用户的复杂性。如果在整个系统中逐段添加 Lambda API,它们可能会在风格上有所不同。首先做一次调查,并制定一个适用于整个图书馆的一致风格,这可能是有益的。
减轻风险的一点是,由于 API 增强在很大程度上彼此独立,因此可以在不影响太多项目的情况下更改范围。也就是说,将范围视为 API 增强的优先列表。根据项目进度和人员配置,将在某个点绘制一条线。如果这一行由于任何原因需要移动,这应该不会对已经完成的工作产生影响,而且几乎不需要重新规划,因为这些项目在很大程度上是相互独立的。出于同样的原因,如果在项目进行期间有新的信息可用,也应该可以在不付出太多努力的情况下重新确定列表的优先级。
将这些新的 API 与现有的 API 混合使用,可能会使用户代码库的维护更加复杂。
依赖
这项工作仅取决于实际的 lambda 实现。
即使这是一个相当软的依赖项,因为 lambdas 在 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 都包含其使用示例。