Guava Futures 中不明确的 lambda 超载

Ambiguous lambda overload in Guava Futures

我相信我遇到了 eclipse bug,但我想确认一下。
我正在使用 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 中显示的错误是这样的:

The method transform(ListenableFuture<Request.Builder>, AsyncFunction<? super Request.Builder,? extends ProtoBufExchange>) is ambiguous for the type 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 编译器以某种方式解决了这种歧义。可能是因为在上面的代码中,如果我们实现的是函数而不是 AsyncFunction,Futures.transform 的 return 类型将与方法 return 类型不匹配。

是eclipse的bug吗?我在这里错过了什么吗?

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

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

“显式类型的 lambda 表达式”和“隐式类型的 lambda 表达式”是有区别的。

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

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

举个简单的例子:

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中指定,但它也包含一个非正式提示:

The informal intuition is that one method is more specific than another if any invocation handled by the first method could be passed on to the other one without a compile-time error.

我认为,很容易看出每个可以由 method(AF) 处理的调用也可以由 method(F) 处理,而相反的情况不适用,即 method((Object x) -> "bla") 调用只能由 method(F) 处理,因此它没有歧义,而 method((Object x) -> new FutureTask<String>(null)) 可以由两者处理,但 method(AF) 更具体

relevant formal part是:

A functional interface type S is more specific than a functional interface type T for an expression e if T is not a subtype of S and one of the following is true (where U₁ ... U<sub>k</sub> and R₁ are the parameter types and return type of the function type of the capture of S, and V₁ ... V<sub>k</sub> and R₂ are the parameter types and return type of the function type of T):

  • If e is an explicitly typed lambda expression (§15.27.1), then one of the following is true:
    • R₂ is void.
    • R₁ <: R₂.

所以在这个涉及显式类型的 lambda 表达式的特定情况下,函数类型的 return 类型已经足以消除歧义。

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

warning: [overloads] <T#1,R#1>method(F<T#1,R#1>) … is potentially ambiguous with <T#2,R#2>method(AF<T#2,R#2>)

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