【问题标题】:Ambiguous lambda overload in Guava Futures番石榴期货中的模棱两可的 lambda 过载
【发布时间】:2017-02-09 16:44:12
【问题描述】:

我相信我在这里遇到了一个 Eclipse 错误,但我想确认一下。
我使用的是java 8(jdk 1.8.0_102),我的代码编译正常,但是eclipse给了我一个错误。

我的代码如下所示:

public ListenableFuture<ProtoBufExchange> myMethod(
   //some code here
   return Futures.transform(future,(Request.Builder reqBuilder) -> {

    //some code here

    return Futures.immediateFuture(exchange);
  }

eclipse中显示的错误是这样的:

方法transform(ListenableFuture, AsyncFunction super Request.Builder,? extends ProtoBufExchange>) 对于 Futures 类型是不明确的。

如果我放一个演员,eclipse不会抱怨:

public ListenableFuture<ProtoBufExchange> myMethod(
   //some code here
   return Futures.transform(future,(AsyncFunction<Request.Builder, ProtoBufExchange>) (Request.Builder reqBuilder) -> {

    //some code here

    return Futures.immediateFuture(exchange);
  }

我知道 guava 15.0 Future.transform() 已重载,有以下两种形式(在较新的 guava 版本上,异步方法具有不同的名称):

transform(ListenableFuture<I> input, Function<? super I,? extends O> function)

transform(ListenableFuture<I> input, AsyncFunction<? super I,? extends O> function)

但是 jdk 编译器以某种方式解决了这种歧义。可能是因为在上面的代码中,如果我们实现的是 Function 而不是 AsyncFunction,Futures.transform 的返回类型将与方法返回类型不匹配。

这是 Eclipse 的错误吗?我在这里错过了什么吗?

关于我的环境的更多详细信息:

jdk:1.8.0_102
日食:4.6.2
番石榴:15.0

【问题讨论】:

  • eclipse 是否使用与在 eclipse 之外运行程序相同的 java 设置?
  • @CKing Eclipse 拥有自己的 Java 编译器 (ecj),因此可以从 Eclipse 和 javac 获得不同的结果。
  • @greg-449 我明白了。我以前没有遇到过这样的情况,所以不知道。
  • 可以说,重载方法 transform(A-&gt;B)transform(A-&gt;F&lt;B&gt;) 天生就可以消除歧义,尤其是在提供隐式 Lamba 作为参数时。抛开语言规范不谈,程序员很难对其进行推理并理解选择了哪种方法。在 Java 中,API 设计者不应该创建这样的重载方法。当然,公平地说,这个 guava API 是在 Java 8 规范最终确定之前设计的。这种情况很像mapflatMap——最好使用2个不同的方法名。

标签: java eclipse lambda java-8 overloading


【解决方案1】:

“显式类型的 lambda 表达式”和“隐式类型的 lambda 表达式”之间存在区别。

name -&gt; expressiorOrBlock(name[,name]*) -&gt; expressionOrBlock 形式的隐式类型 lambda 表达式需要其上下文(即已解析方法)来确定其类型,因此不用于消除重载方法的歧义。这样做并非不可能,但由于由此产生的复杂性,规范明确排除了它。

(Type name[, Type name]*) -&gt; expressionOrBlock 形式的显式类型 lambda 表达式具有确定其功能签名所需的一切,包括它们的返回类型,这允许使用它们来消除重载方法的歧义。

举个简单的例子:

interface F<T,R> {
    R apply(T t);
}
interface AF<T,R> {
    Future<R> apply(T t);
}
static <T,R> Future<R> method(F<T,R> f) {
    return null;
}
static <T,R> Future<R> method(AF<T,R> f) {
    return null;
}
public static void main(String[] args) {
    // these two don't compile
    method(x -> "bla");
    method(x -> new FutureTask<String>(null));

    // these two do compile
    method((Object x) -> "bla");
    method((Object x) -> new FutureTask<String>(null));
}

正式规则在JLS, §15.12.2.5. Choosing the Most Specific Method中指定,但也包含非正式提示:

非正式的直觉是,如果第一个方法处理的任何调用都可以传递给另一个方法而不会出现编译时错误,那么一个方法比另一个方法更具体。

我认为,很容易看出,method(AF) 可以处理的每个调用也可以由method(F) 处理,而相反的情况则不适用,即method((Object x) -&gt; "bla") 调用只能由@ 处理987654330@,所以它并不模棱两可,而method((Object x) -&gt; new FutureTask&lt;String&gt;(null)) 两者都可以处理,但method(AF) 更具体

relevant formal part 是:

如果T 不是S 的子类型并且以下其中一项为真(其中@987654338 @...U<sub>k</sub>R₁S捕获的函数类型的参数类型和返回类型,V₁...V<sub>k</sub>R₂是参数类型和返回T的函数类型的类型):

  • 如果 e 是显式类型的 lambda 表达式(第 15.27.1 节),则以下情况之一为真:
    • R₂void
    • R₁&lt;:R₂.

因此,在这种涉及显式类型化 lambda 表达式的特定情况下,函数类型的返回类型已经足以消除歧义。

但请注意,编译示例代码也会产生警告:

警告:[overloads] &lt;T#1,R#1&gt;method(F&lt;T#1,R#1&gt;) … 可能与 &lt;T#2,R#2&gt;method(AF&lt;T#2,R#2&gt;) 不明确

提醒隐式类型的 lambda 表达式对于此类重载方法总是模棱两可,而且使用显式类型的 lambda 表达式的方法选择可能不直观。

【讨论】:

  • 感谢您的回复。老实说,我没有看懂正式的规范,但如果你的理解是正确的:“函数类型的返回类型已经足以消除歧义”,那么这就解释了为什么代码确实可以编译。这也使我们得出结论,这确实是 eclipse 编译器中的一个错误。对吗?
  • 没错。我知道,正式的规范很难阅读。这就是为什么我还引用了“非正式直觉”部分。
猜你喜欢
  • 2015-03-11
  • 2013-07-14
  • 2016-09-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-04
  • 1970-01-01
相关资源
最近更新 更多