Lambda 从 Java 8 开始加入 Java 语言,这需要修改类型推导来支持它们。但是之前的改变并没有达到它们的需求,而部分原因是那些改变可能会让新接触 Lambda 的开发者困惑。不过现在情况正在发生变化,在上下文提供了足够多信息的情况下,编译器仍然不能推导 Lambda 的类型会让开发者们沮丧。下面的例子说明了这个时候的 Lambda 类型推导:
// 情况 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()); }
在某些情况下,预期 Lambda 会有多个参数,虽然代码块中不会完全用到,却要开发者为这些不用的参数命名。这个改变允许使用下划线来表示不使用的参数。
Function<String, Integer> noneByDefault = notUsed -> 0; // 当前 Function<String, Integer> noneByDefault = _ -> 0; // 提议
这个特性已经存在于其它一些语言,比如 Scala、Ruby 或 Prolog。不过到 Java 7,这都并不容易实现, 因为下划线是一个合法的标识符,所以代码中可能会用到。为了在不引起大量重写代码的前提下引入这个改变, 就不能操之过急:
Java 8:如果下划线用作标识符,会产生一个警告,告诉开发者避免使用它;在 Lambda 中不允许使用下划线(这不会引起向后兼容的问题,因为 Lambda 是 Java 8 引入的)。
Java 9:前面提到的警告已经转变为错误,这确保 Java 代码中不使用下划线作为标识符。
Java 10 (及以后):下划线再次可用作标识符,但它只能作为 Lambda 表达式的参数使用。
从一开始就并非所有人都一致支持这个改变;有些用户喜欢新提议带来的简洁语法,而另外一些人则喜欢使用明确的名称。进一步讨论也许能达成共识。
[译者注:shadow variable,通常译为隐藏变量。它是指在某个用域中定义的变量名与它直接的外部作用域的某个变量重名,那么在当前作用域,这个变量就隐藏外部作用域的同名变量。shadow parameter 意义近似。]
也许这是新提议中最有争议的一个功能。目前 Lambda 的参数不能隐藏外部变量,这就意味着在当前作用域内必须选择与其它可访问变量不同的名称;这与其它封装的作用域工作原理类似,比如 while 循环或 if 语句:
String s = "hello"; if(finished) { String s = "bye"; // 错误,s 已经定义了 } Predicate<String> ps = s -> s.isEmpty(); // 错误,s 已经定义了
如果这个提议被接受,Lambda 的参数就可以隐藏外部已存在的标识符并再次使用它。它的好处是某些情况下不再需要一个意义不太明确的 Labmda 参数名(对上面的例子的一个典型的修正是 s2 -> s2.isEmpty())。不过它也可能带来像 Roy Van Rijn,国际有名的演说家,提到的潜在错误,它提到:
Map<String, Integer> map = /* ... */ String key = "theInitialKey"; map.computeIfAbsent(key, _ -> { String key = "theShadowKey"; // 影子变量 return key.length(); });
评论删除后,数据将无法恢复
评论(14)
引用来自“OSC首席酱油党”的评论
再搞搞Scala就该哭了引用来自“挖红薯”的评论
这是要改成go语言的语法吗😓