文档结构  
翻译进度:已翻译     翻译赏金:0 元 (?)    ¥ 我要打赏

新的 JEP 对增强 Lambda 提出了修改建议,包括消除歧意、用下划线表示未使用的参数、影子外部变量 等。虽然这些改变会让 Java 的 Lambda 更接近于其它语言的 Lambda,不过最初的讨论只是想支持混合水平。这个 JEP 补充了一系列的提议来改善 Java 语言,包括局部变量的类型推断增强的枚举,这些内容都有可能包含在 Java 10 中。

尽管与 Lambda 相关的改变只有 3 个,但它们之间并没什么联系,它们中哪个更受欢迎完全取决于反馈。因此,我们将在本文中分别进行说明。

第 1 段(可获 1.51 积分)

更好的消除歧义

Lambda 从 Java 8 开始加入 Java 语言,这需要修改类型推导来支持它们。但是之前的改变并没有达到它们的需求,而部分原因是那些改变可能会让新接触 Lambda 的开发者困惑。不过现在情况正在发生变化,在上下文提供了足够多信息的情况下,编译器仍然不能推导 Lambda 的类型会让开发者们沮丧。下面的例子说明了这个时候的 Lambda 类型推导:

第 2 段(可获 1.26 积分)
// 情形1: lambda表达式被推断为Predicate<String>类型;
//        因此第一个重载函数会被调用。
private void m(Predicate<String> ps) { /* ... */ }
private void m(Function<String, String> fss) { /* ... */ }

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

// 情形2: 没有足够的信息来独立推断lambda表达式的类型,
//       但是m2也没有重载。
//       方法参数的签名会被用来推断lambda表达式的类型,
//       推断结果是s是String类型的,并且s.length()会返回Integer类型的值
private void m2(Function<String, Integer> fsi) { /* ... */ }
private void callingM2() {
    m2(s -> s.length());
}

// 情形3: 没有足够的信息来独立推断lambda表达式的类型。
//       m3被重载了,但是不同的版本有不同的参数个数,
//       只有第一个匹配参数的数目(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());
}

// 情形4: 没有足够的信息来独立推断lambda表达式的类型。
//       m4的多个重载版本拥有同样的参数个数。
//       属于模棱两可的调用,会报错误
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 返回 boolean,那么m4的第二个重载就不能匹配了,排除掉它。
  4. 仅剩的选择是m4的第一个重载,它跟lambda表达式的推断类型相匹配,因此这个就会被使用。
第 3 段(可获 1.39 积分)

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

用下划线表示不用的参数

在某些情况下,预期 Lambda 会有多个参数,虽然代码块中不会完全用到,却要开发者为这些不用的参数命名。这个改变允许使用下划线来表示不使用的参数。

Function<String, Integer> noneByDefault = notUsed -> 0; // currently
Function<String, Integer> noneByDefault = _ -> 0; // proposed

这个特性已经存在于其它一些语言,比如 Scala、Ruby 或 Prolog。不过到到 Java 7,这都并不容易实现, 因为下划线是一个合法的标识符,所以代码中可能会用到。为了在不引起大量重写代码的前提下引入这个改变, 就不能操之过急:

第 4 段(可获 1.61 积分)
  1. Java 8:如果下划线用作标识符,会产生一个警告,告诉开发都避免使用它;在 Lambda 中不允许使用下划线(这不会引起向后兼容的问题,因为 Lambda 是 Java 8 引入的)。
  2. Java 9:前面提到的警告已经转变为错误,这确保 Java 代码中不使用下划线作为标识符。
  3. Java 10 (及以后):下划线再次可用作标识符,但它只能作为 Lambda 表达式的参数使用。

从一开始就并非所有人都一致支持这个改变;有些用户喜欢新提议带来的简洁语法,而另外一些人则喜欢使用明确的名称。进一步讨论可能会达成共识。

第 5 段(可获 1.63 积分)

影子参数

也许这是新提议中最有争议的一个功能。目前 Lambda 的参数不能隐藏外部变量,这就意味着在当前作用域内必须选择与其它可访问变量不同的名称;这与其它封装的作用域工作原理类似,比如 while 循环或 if 语句:

String s = "hello";

if(finished) {
    String s = "bye"; // error, s already defined
}

Predicate<String> ps = s -> s.isEmpty(); // error, s already defined

如果这个提议被接受,Lambda 的参数就可以隐藏外部已存在的标识符并再次使用它。它的好处是某些情况下不再需要一个意义不太明确的 Labmda 参数名(对上面的例子的一个典型的修正是s2 -> s2.isEmpty())。不过它也可能带来像 Roy Van Rijn,国际有名的演说家,提到的潜在错误,它提到

 

第 6 段(可获 1.45 积分)
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 的改进。

 

第 7 段(可获 1.1 积分)

文章评论