跳到主要内容

JEP 109:使用 Lambda 增强核心库

QWen Max 中英对照

概述

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

目标

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

第二个目标是通过在库 API 中使用 Lambda,从实际代码调用它们,评估结果,并向 Lambda 语言/编译器团队提供反馈,以此来指导 Lambda 语言特性的设计。

目标可以概括如下:

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

非目标

并非所有可能使用 Lambda 的地方都以使用它为目标,其范围也不会超出核心库。例如,客户端、XML 和 CORBA 不在此工作的涵盖范围内。

此增强功能不会为核心类添加太多新功能 —— 只是完成熟悉任务的新方法。

对于升级使用 Lambda 的 API 比例,没有具体的目标,也就是说,没有任何类似“我们会为 xx% 的库添加 Lambda”的计划。

成功指标

成功与否将根据开发者对新 API 和功能的采用程度来评估。完全成功的标准是,Java 开发者在使用核心库时,Lambda 功能的使用将成为默认的习惯用法。

动机

“语言设计就是库设计。
库设计就是语言设计。” —— Andrew R. Koenig

Java 8 将包含一个名为 Lambda 的新语言特性。在语言中拥有这一特性非常有用,但仅靠针对 Lambda 的语言改动,该平台仍是不完整的。在库 API 的适当部分添加 Lambda 支持,将使该平台的价值大大提高。

在过去的几年中,出现了各种各样的新编程语言,并且它们正在变得流行。这些语言大多数都有某种块、闭包或者一等函数结构。尽管 Java 仍然是 排名第一的编程语言,但普遍的观点认为它没有跟上编程语言的最新发展。这一点作为 Lambda 语言特性的动机已被广泛认可。然而,也有必要考虑支持使用 Lambda 的库 API。替代语言都有库,它们的 API 被调整为能够很好地与语言提供的闭包或函数对象配合工作——既流畅又符合习惯用法。

QWen Max 中英对照

同样,对于 Java,我们认为随着时间的推移,Lambda 的使用将会变得广泛,并且会围绕这一特性发展出各种编码习惯用法。Java 库的 API 将需要增强,以支持与当前传统用法并存的 Lambda 习惯用法。

描述

该项目分为两个阶段:

  1. 一个调查阶段,用于确定 Lambda API 增强功能的候选者;以及
  2. 一个范围界定与实施阶段,用于优先考虑并选择部分候选者,并实现这些功能。

候选人调查

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

以 Ruby 为例。Ruby 语言支持多种一流的类函数构造。这些构造本身非常有用,因为它们使程序员能够使用函数式编程风格、创建高阶函数、组合函数等。此外,由于 Ruby 的类库是与支持一流函数的语言一同开发的,因此类库中的许多 API 都利用了这些函数。我们可以通过浏览 Ruby 类库 来找到一些示例,这些示例可以作为改进 Java 类库的灵感来源。以下是 Ruby 类库中的一些例子:

  • Integer 类具有 timesuptodowntostep 方法,这些方法支持各种算术迭代结构。这消除了在语言中定义多种此类迭代结构的必要性。
  • 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() 方法,即使它不是一个集合。例如,可以在 java.nio.file.Files 中添加一个 forEachLine() 方法,该方法对文件中的每一行调用一个 Lambda 表达式。
  • 寻找使用“执行环绕”惯用法的机会。例如,文件在使用后必须关闭,锁在使用后必须解锁等。
  • 查找实现了 Iterable 接口的类,并考虑 Lambda 是否有助于反转控制流。
  • 在代码中查找 for 循环和 while 循环,并考虑使用 Lambda 来反转控制流。
  • 识别具有一个或两个抽象方法的抽象类,并考虑添加接受 Lambda 的构造函数或工厂方法。对于接口也可以考虑这样做。这种模式是通过重写来“填充”实现;考虑如何将其转换为将 Lambda 作为参数传递。

范围界定与实施

最终的候选集合可能会太长,无法在 JDK 8 的时间框架内实现。它们需要进行优先级排序,然后根据可用的时间表和分配给该项目的一组资源(人员)进行截断。优先级排序可以依据库的主观重要性,但最好有一些使用数据作为支持。例如,对 Qualitas Corpus 进行调查可能会有助于确定库中哪些领域使用得最多。

语言设计问题

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

在使用 Lambda 进行 API 开发时,应考虑以下问题,并将相关信息反馈给 Lambda 语言设计团队。
markdown
  1. 异常透明性:你是否觉得需要编写能够抛出受检异常的 lambda 表达式?是否需要额外的一组带有 throws 子句的 SAM 类型,甚至是泛型 throws E?结果是否变得极其繁琐?
  2. 方差:函数式接口的实用工具方法可能会比 API 中的其他任何部分更频繁地使用通配符;这是否极其繁琐?是否有必要提供更好/更简洁的方差支持?
  3. 未装箱覆盖:在设计函数式接口时,是否有必要允许用 primitive/void 返回类型覆盖引用返回类型?例如,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(Java 规范请求);它们应涵盖在平台 JSR 之下。所需的新类(函数式接口)将由 JSR 335 EG 设计和审查。当然,必须遵循所有适当的审查流程。
  • 其他 JDK 组件:Lambda 实现
  • 兼容性:API 的更改不太可能引入不兼容性。
  • 文档:可能需要一些新的教程来展示如何将 Lambda 与新的库结构一起使用,或者提供一系列小示例来说明常见用法。在浏览 javadoc 时,Lambdas 可能难以辨认,因此可能有必要采用一些新的 javadoc 语法或不同的 javadoc 输出格式,以某种独特的方式突出显示使用 lambda 的 API。另一个可能性是确保每个新的接受 Lambda 的 API 的 javadoc 都包含一个使用示例。