BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

Java 10将带来升级版的Lambda

| 作者 Abraham Marín Pérez 关注 8 他的粉丝 ,译者 尚剑 关注 2 他的粉丝 发布于 2017年1月23日. 估计阅读时间: 10 分钟 | 都知道硅谷人工智能做的好,你知道 硅谷的运维技术 也值得参考吗?QCon上海带你探索其中的奥义

一个新的JEP将用于增强lambda功能,提出的更改包括更好的消岐、对未使用的参数用下划线标注和外部变量的跟踪。虽然这些更改会使Java中的lambda表达式更类似于其它语言,但是初步讨论表明大家都不同程度地支持这个方案。这个JEP补充了一系列其他建议来改进Java语言,包括局部变量类型推断增强的枚举,所有这些改进都可能出现在Java 10中。

尽管上述三个更改都与lambda功能有关,但它们之间是相互独立的,其中一些可能会被舍弃,而其他的则取决于反馈情况。因此,我们将在本文中单独解释它们。

更好的消歧

当在Java 8中添加lambda功能时,必须修改类型推断以支持它们。然而,过去进行的更改并没有达到预期的效果,部分原因是担心这些更改会使第一次接触lambda的开发者感到困惑。但是,从这一点来说,情况似乎正在改变,并且如果上下文提供了充足的信息,而编译器无法推断lambda的类型时,一些开发者会感到沮丧。以下例子说明了lambda类型推断如何工作:

// Case 1: 推断为Predicate <String>的lambda类型
//         第一个重载方法将被调用
private void m(Predicate<String> ps) { /* ... */ }
private void m(Function<String, String> fss) { /* ... */ }

private void callingM() {
    m((String s) -> s.isEmpty()); 
}

// Case 2: 没有足够的信息来独立地推断lambda的类型
//         不过m2没有被重载
//         将方法参数的签名应用于lambda来推断
//         s是String类型并且s.length()返回Integer类型
private void m2(Function<String, Integer> fsi) { /* ... */ }
private void callingM2() {
    m2(s -> s.length());
}

// Case 3: 没有足够的信息来独立地推断lambda的类型
//         m3是重载的,但不同的版本有不同的参数数量
//         只有第一个版本和parameters (1)的数量匹配
//         将方法参数的签名应用于lambda来推断
//         s是String类型并且s.length()返回Integer类型
private void m3(Function<String, Integer> fsi) { /* ... */ }
private void m3(Function<String, Integer> fsi, String s) { /* ... */ }

private void callingM3() {
    m3(s -> s.length());
}

// Case 4: 没有足够的信息来独立地推断lambda的类型。
//         m4的多个重载版本具有相同数量的可用参数
//         Ambiguous call, ERROR
private void m4(Predicate<String> ps)  { /* ... */ }
private void m4(Function<String, String> fss)  { /* ... */ }

private void callingM4() {
    m4(s -> s.isEmpty());
}

然而,对于最后一种情况来说,有足够的信息来判断m4的第一个重版本就是被调用的那个,尽管编译器当下并没有使用该信息。根据新建议,编译器可以按照以下步骤来解决歧义:

  1. 这两种可能性都假定lambda的参数是一个String,所以s可以被认为是String类型
  2. 现在我们知道s是一个String,我们知道方法String.isEmpty()返回boolean
  3. 由于lambda返回booleanm4的第二个变体不匹配,因此它被舍弃
  4. 只剩下一个选项就是m4的第一个变体,它与lambda的推断类型匹配,因此使用第一个变体

类似的论证也可以应用于方法引用。

用下划线表示未使用的参数

某些情况下,我们希望得到具有多个参数的lambda表达式,尽管不会使用所有的参数,但这要求开发者使用指示性名称来表示未使用的参数。这个更改将允许使用下划线来表示未使用的参数。

Function<String, Integer> noneByDefault = notUsed -> 0; // 目前
Function<String, Integer> noneByDefault = _ -> 0; // 建议

这是一个其他几种语言都有的功能,如Scala、Ruby或Prolog,但是,在Java中这不能轻易地实现,因为直到Java 7,下划线仍然是一个有效的标识符,它可以在代码其它地方使用。为了引入这种更改而不需要重写大量的代码,这一功能需要逐步完善:

  1. Java 8:当下划线用作标识符时,会发出警告,阻止开发人员使用它;下划线不允许作为lambda中的标识符(这不会导致任何向后不兼容问题,因为在Java 8之前的版本并没有lambda功能)。
  2. Java 9:Java 8中的警告变为错误,确保将使用下划线作为标识符从Java代码语料库中删除。
  3. Java 10(或更高版本):下划线作为标识符重新引入,但仅适用于lambda表达式中未使用的参数。

通过初步的讨论来看,对这一更改的意见似乎并不一致;一些用户喜欢新建议的简洁性,但另一些用户喜欢使用明确的名称。在进一步的讨论中可能达成共识。

参数的隐藏

这也许是新提出的功能中最有争议的一个。当前lambda参数不允许影响外部变量,意味着参数名称必须不同于当前作用域中可访问的任何变量;这与其他包含作用域保持一致,比如while循环或if语句:

String s = "hello";

if(finished) {
    String s = "bye"; // 错误,s已经定义
}

Predicate<String> ps = s -> s.isEmpty(); // 错误,s已经定义

如果建议的更改继续下去,lambda参数将能够重复使用和隐藏现有标识符。在某些情况下,这可能有利于用户,也就是说他们不需要使用其他不太直接的名称作为他们的lambda参数名称(上面的例子通常会被重写为s2 - > s2.isEmpty()),但是,就像国际知名演讲者Roy Van Rijn提出的那样,它也可能引入微小的错误:

Map<String, Integer> map = /* ... */
String key = "theInitialKey";

map.computeIfAbsent(key, _ -> {
   String key = "theShadowKey"; // shadow variable
   return key.length();
});

目前,上述代码是不被允许的,但根据新提案这也可能是对的。如果标记为“shadow variable”的行被删除,代码将仍然可以编译和运行,但它会做完全不同的事情。

为了评估上述更改是否将被引入Java以及将以什么形式引入,他们还将进行更多的讨论。然而,毫无疑问的是,在Java 8中引入lambda只是未来Java语言的众多改进的第一步。

查看英文原文:Java 10 Could Bring Upgraded Lambdas


感谢薛命灯对本文的审校。

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ@丁晓昀),微信(微信号:InfoQChina)关注我们。

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我
社区评论

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT