写点什么

专访 Brian Goetz:Project Lambda 探秘

2013 年 8 月 21 日

Oracle 于四月宣布将万众瞩目的 Java 8 推迟到 2014 年第一季度发布(参见InfoQ 的报道)。

Oracle Java 平台小组的首席架构师 Mark Reinhold 在其博客中这样说道:

“在过去6 个月导致进度拖延的最主要工作是关于Project Lambda,这个该版本唯一重大特性的。

去年年底我们集成了语言和虚拟机为支持Lambda 所做的修改,但在处理所有相关变动和安全工作时,我们比预期花了更长的时间来完成stream API 的收尾以及相关核心库的增强。”

InfoQ 采访了 Oracle 的 Brian Goetz(JSR-335 规范的负责人),让他从内部视角发表了对 Project Lambda 的看法。

InfoQ**:JSR-335项目有很多项目需要协同,从并行集合到新的 Stream API。您能否在不涉及任何机密的情况下,以内部人员的视角透露一些内幕?你们是如何管理这些协同工作的?**

Brian:确实,这些改动部分之间的交互数目让人生畏,并且还有很多离散的部分:两个相关的专家组列表(一个为纯粹的语言特性,另一个包含库特性 JSR-166 专家组的成员)、OpenJDK 社区和 Oracle Java 平台组的多组件小组。但其回报也是十分丰厚的:与之前只能通过编译器语法糖来实现所有特性的平台演进工作不同,JSR-335 可以实现语言、库和虚拟机的协同进化,产生更好的结果。

在我看来,成功的关键是对目标保持清晰地关注。语言特性本身并不是一个目标。它们是推动者,肯定或否定某种风格和惯用法。即便在“添加 Lambda 表达式”这个范畴内,也常常会有影响方法的隐藏目标。 BGGA 提案的一个潜在目标是通过库来支持控制抽象。CICE 更多地关注更加适当的目标(减轻使用内部类的语法之殇)。而在 C#引入 Lambda 之时,更多的使用场景是用于 LINQ

对于 JSR-335 来说,我们清楚地认识到,语言特性是创建优秀库函数(如在现有集合上执行批量数据并行操作)的重要手段。语言特性造就了优秀的库函数,而优秀的库函数造就了简洁、清晰、不易出错的用户代码。因此我们收集了一系列想要实现的用户惯用语,如在集合上执行批量数据并行操作时,可以像下面这样:

复制代码
int sumOfWeights =
anArrayList.parallelStream()
.filter(b -> b.getColor() == BLUE)
.mapToInt(b -> b.getWeight())
.sum();

我们使用这些示例作为标准来确保我们在向着目标前进,而不仅仅是前进。

这个简单的示例暗示了:

  • 需要在表达式中引入紧凑的编码行为(代码即数据)。
  • 需要将迭代的控制从客户端(使用 for 循环这样的语言特性)转移到库函数中(内部迭代)。
  • 需要在已有类型中添加新的方法,如 List.parallelStream(接口改变)。
  • 需要具有更好的类型推断(使得编译器可以推断 Lambda 形参的类型)。
  • 需要在“非线程安全”的数据结构上执行并行操作。

在实现一个大型语言特性时,所要面临的最严峻的挑战之一就是“特性蔓延”。在完成一个重大改变后,你总是希望继续引入其他的特性。(我们收到了来自社区的成百条建议,并且不得不对几乎所有建议说不。)只有对目标有清晰地认识,才能做出“这很酷,但超出了我们的范畴”的决定。

另一个关键的协同工具是在设计语言特性时使用正式的数学模型,如默认方法和类型推断行为的继承规则。自然语言特别不适合讨论这些复杂的问题,必然会导致误解。使用正式模型可以简单明了地讨论和理解一个特性的实际语义,并为规范、实现和测试提供蓝图。

InfoQ**:大多数 Project Lambda的活动似乎都属于 OpenJDK。如何提高社区参与度?**

Brian:在 Lambda-dev 邮件列表里有一批早期试用者,他们会定期下载最新构建(或从源代码构建)并尝试新的特性。这种体验驱动的反馈对于项目进程来说是至关重要的。早期试用者会在纠正成本尚低的时候找出 bug 和使用问题。社区能做的最有价值的帮助是:自己试写代码,并报告其体验(积极的或消极的)。

InfoQ****:Java诞生之后,除了 Invoke-Dynamic外,几乎所有的 JSR都涉及编译期变动。JSR 335有哪些字节码级别的变动呢?

Brian:Lambda 表达式严重依赖于 Java SE 7 中的 invokedynamic。有了 invokedynamic,编译器的开发者们可以避免很多虚拟机方面的工作。不过在我们添加的特性中,有两个是与虚拟机息息相关的,它们是默认方法(虚拟机用于继承,并最终影响 invokevirtual、invokeinterface 和 invokespecial 字节码的语义)和接口静态方法(大多会放宽现有类文件的限制)。

InfoQ**:貌似 IntelliJ Idea已经实现了一些 JSR-335兼容。是不是 IDE供应商们前段时间已经拿到了预览版本来进行开发?能否预测一下什么时候 NetBeans和 Eclipse会开始支持 JSR-335?**

Brian:我们确保所有的 IDE 供应商都派代表参加专家组,不但是为了确保这些特性能够被工具支持,同时 IDE 供应商还能较早地访问规范。NetBeans 很早以前就有了允许 Lambda 的构建版本,由于使用 javac 作为编译器,所以它们在这方面已经取得了领先。IntelliJ 已经在预览版和即将发布的 12.x 中取得了重大的 JSR-335 支持。当然,所有这一切都只能等规范发布后才能浮出水面。在那之前,什么都不确定。

InfoQ:Lambda 中一个令人激动的特性是,有了为管道和并发集合而设计的 Stream API,能否谈一下它们是如何设计、实现和协作的?

Brian:我们先考虑用户怎么使用方便,反过来再考虑设计。我们参考了其他语言的库,也包括 Java 的库如 LambdaJ。我们特别注意了它们的“展示”示例,并将其作为“需求”,来探索什么样的模型能支持它们。我们还发现以下三项是十分重要的,即在顺序和并发两种情况下都能使用流操作、现有集合可用作流的源,甚至包括非线程安全的集合用作并发流的源。我们为 API 设计制定了三个不同的迭代,每个迭代都基于上一个迭代。

InfoQ:常常会有人争论 Lambda和闭包的区别是什么。您的观点呢?

Brian:我认为考虑 Java SE 8 中的 Lambda 是不是“真正的”闭包是毫无意义的。对我来说,这里的“真正的”属于语法错误。很多语言都具有类闭包的构造,和闭包有着不同程度的相似和区别。认为 X 语言定义了真正的闭包,而其他语言如果没有完全相同的实现就不是真正的闭包是无益的。

也就是说,你很难决定是否支持捕获可变局部变量、非局部控制流、异常透明度和其他的特性,并且放弃其中一些特性意味着它们很难自然地表达。这是表现性和复杂度之间的权衡,我们希望将复杂性预算全部投入到对开发者最有影响的特性上。我们可以谈论这些决定的优缺点,但往“真假闭包”上扯还是算了吧。

InfoQ:闭包似乎给接口带来了一个悖论:我们平时所说的接口是指可能包含数据和结构但缺少实现的结构。而另一方面,闭包是作为数据的实际实现。Project Lambda是否允许我们通过定义常量闭包字段来在接口内实现一些功能呢?

Brian:代码即数据。(哥德尔 100 年前就这么教导我们。)Lambda 表达式只不过是让那些可简单地视为数据的行为,在语法上更加容易表示。

也就是说,接口缺乏实现的概念在 Java SE 8 中发生了改变。为了支持接口进化,我们允许接口提供可被类继承的方法的默认块,同时还允许在接口中定义静态方法。(接口包含默认方法可看做是某种形式的无状态 trait。)

InfoQ**:Lambda引入了一个“Optional”类型。它如何帮助我们避免空引用?**

Brian:我们对 Optional 的使用十分有限。它实际上只是用于方法的返回类型(该方法可能不返回任何东西)。比如 Map.get(key) 方法,如果 map 中不包含指定的键就会返回 null。这有两个缺点。首先,null 可能是某个 map 元素的有效值(某些 map 允许这样)。现在你无法区分“不存在”和“映射到 null”,而如果不得不再调用 containsKey() 来决定是哪种情况的话,你已经引入了一个竞态条件。其次,在代码中忘记检查 null 实在是太常见了,这使得代码缺乏可靠性。(并且 null 检查还会使代码变得丑陋。)当然,修改 Map.get() 为时已晚,但我们不会让错误再次发生。

Optional 可以描述一个非空值,或显式地设置为 empty。你不能对返回 Optional 的方法进行盲目地解引用,因为类型系统不允许。因此你只能显式地决定如果 optional 为 empty 时怎么处理。Optional 包含 get()(如果 Optional 为 empty 则抛出异常)、getOrElse(defaultValue)、getOrThrow(Supplier等方法。因此你可以显式但却不冒失地指定如果值不存在时应该如何处理——抛出异常或返回默认值。

也就是说,熟悉 Scala 的 Option 的人可能要失望了。这并不是对类型系统多么深入的修改,它只是库中的一个辅助类,用来更加显式地表示“该方法可能不会返回任何东西”,而不是使用 null。

要了解最新的 Project Lambda 的情况,请访问 OpenJDK 网站上的 Project Lambda 页面

关于受访者

Brian Goetz 是 Oracle 的 Java 语言架构师,也是 JSR-335(Lambda Expressions for the Java Language)规范的负责人。他是畅销书《 Java 并发编程实战》的作者,并且经常出席各大业内会议。

查看英文原文 Project Lambda from the Inside. An Interview with Brian Goetz

2013 年 8 月 21 日 10:436135
用户头像

发布了 59 篇内容, 共 18.6 次阅读, 收获喜欢 3 次。

关注

评论

发布
暂无评论
发现更多内容

Redis 6.0 新特性-多线程连环13问!

牧码哥

redis 多线程 io

婚姻就是合伙开公司,各自做好自己的工作很重要

鼎玉谷

管理 婚姻 公司 付出 人情

如何利用数据异构实现多级缓存或者数据迁移

松花皮蛋me

缓存 分布式 分库分表

面试指南 | 终于要跟大家见面了,我有点紧张。

Apache Flink

大数据 flink 流计算 实时计算 大数据处理

开始每周写作计划

M1racle

习惯与惯性

伯薇

个人成长 习惯 习惯养成 提升能力

写作的意义到底是什么

七镜花园-董一凡

写作

翻译和产品本地化的区别是什么?

葛仲君

翻译 本地化 全球化 产品开发

C++线程池的实现

helloworld

c++ C# 线程池

centos7 maven私服自动启动

kcnf

金融「中台」十宗罪

fino星君

中台 企业中台 业务中台

DDD 实践手册(4. Aggregate — 聚合)

Joshua

设计模式 领域驱动设计 架构模式

笔记:《如何系统思考》之如何做到系统思考

wiflish

思维方式

Clickhouse 性能测试

久吾尔岂

对你来说,阅读是另一种生活的方式吗?

叶小鍵

如何对Code Review的评论进行分级

宝玉

代码审查 Code Review

重新认识Go语言中的slice

麻瓜镇

golang

读懂才会用 : Redis的多线程

小眼睛聊技术

Java redis 学习 程序员 编程语言 后端

内存对齐

helloworld

c c++ C#

Service Worker in Action

xgqfrms

Service Worker Web Worker

业务代码的救星——Java 对象转换框架 MapStruct 妙用

周三不加班

MapStruct 对象转换

C++定时器的实现

helloworld

c c++ C#

浅谈SpringCloud之服务注册中心Eureka

北漂码农有话说

一个平凡程序员的年度总结

小智

程序员 人生

MySQL中 int(11)和 int(10) 到底有没有区别?

周三不加班

MySQL 字符宽度 数据库数据类型

机会是留给不停寻找他们的人,而不是原地等待的人

非著名程序员

程序员 提升认知 机会 行动派

写字工具更新史

Bonaparte

学习 读书笔记

《TCP/IP详解》概述

网瘾少年SEC

TCP 网络协议 IP

当我们谈到ThreadLocal的时候,我们在谈什么?

Jason

Java 多线程 ThreadLocal

关于GDB你需要知道的技巧

helloworld

c c++ C#

各大公司面试题分类整理

是小毛吖

后端 面试题

演讲经验交流会|ArchSummit 上海站

演讲经验交流会|ArchSummit 上海站

专访Brian Goetz:Project Lambda探秘-InfoQ