Java 如何知道使用 lambda 表达式调用哪个重载方法? (供应商、消费者、可调用……)

How does Java know which overloaded method to call with lambda expressions? (Supplier, Consumer, Callable, ...)

首先,我不知道如何恰当地表达这个问题,所以这需要建议。

假设我们有以下重载方法:

void execute(Callable<Void> callable) {
    try {
        callable.call();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

<T> T execute(Supplier<T> supplier) {
    return supplier.get();
}

void execute(Runnable runnable) {
    runnable.run();
}

从这个 table 出发,我从

Supplier       ()    -> x
Consumer       x     -> ()
BiConsumer     x, y  -> ()
Callable       ()    -> x throws ex
Runnable       ()    -> ()
Function       x     -> y
BiFunction     x,y   -> z
Predicate      x     -> boolean
UnaryOperator  x1    -> x2
BinaryOperator x1,x2 -> x3

这些是我在本地得到的结果:

// Runnable -> expected as this is a plain void  
execute(() -> System.out.println()); 

// Callable -> why is it not a Supplier? It does not throw any exceptions..
execute(() -> null);

// Supplier -> this returns an Object, but how is that different from returning null?
execute(() -> new Object());

// Callable -> because it can throw an exception, right?
execute(() -> {throw new Exception();});

编译器如何知道调用哪个方法? 例如,它如何区分 CallableRunnable

除了 () -> null 我认为是 Callable 之外,这一切都说得通并且有一个简单的模式。 RunnableSupplier/Callable 明显不同,因为它没有输入和输出值。 CallableSupplier 之间的区别是 Callable 你必须处理异常。

() -> null 毫无例外地是 Callable 的原因是您定义的 return 类型 Callable<Void>。它要求您 return 对某个对象的引用。 Void 对 return 的唯一 possible 引用是 null。这意味着 lambda () -> null 正是您的定义所要求的。如果您删除 Callable 定义,它也适用于您的 Supplier 示例。但是,它使用 Callable<Void> 而不是 Supplier<T>,因为 Callable 具有确切的类型。

选择

Callable 而不是 Supplier 因为它更具体(正如已经建议的评论)。 Java Docs 声明它尽可能选择最具体的类型:

Type inference is a Java compiler's ability to look at each method invocation and corresponding declaration to determine the type argument (or arguments) that make the invocation applicable. The inference algorithm determines the types of the arguments and, if available, the type that the result is being assigned, or returned. Finally, the inference algorithm tries to find the most specific type that works with all of the arguments.

我相信我已经找到了官方文档中描述的地方,虽然有点难以阅读。

Here 提到:

15.27.3. Type of a Lambda Expression

Note that while boxing is not allowed in a strict invocation context, boxing of lambda result expressions is always allowed - that is, the result expression appears in an assignment context, regardless of the context enclosing the lambda expression. However, if an explicitly typed lambda expression is an argument to an overloaded method, a method signature that avoids boxing or unboxing the lambda result is preferred by the most specific check (§15.12.2.5).

然后here (15.12.2.5)分析地描述了如何选择最具体的方法。

所以按照这个举例说明

One applicable method m1 is more specific than another applicable method m2, for an invocation with argument expressions e1, ..., ek, if any of the following are true:

m2 is generic, and m1 is inferred to be more specific than m2 for argument expressions e1, ..., ek

所以

// Callable -> why is it not a Supplier?
execute(() -> null);   <-- Callable shall be picked from 2 options as M2 is generic and M1 is inferred to be more specific

void execute(Callable<Void> callable) {  // <------ M1 
   try {
    callable.call();
  } catch (Exception e) {
      e.printStackTrace();
  }
}


 <T> T execute(Supplier<T> supplier) {  // <------ M2 is Generic
    return supplier.get();
 }

为什么推断 M1 更具体可以从描述的这个过程中追溯 here (18.5.4 More Specific Method Inference)

// Callable -> why is it not a Supplier? It does not throw any exceptions..
execute(() -> null);

这是因为Callable<Void>方法和Supplier<T>方法都是applicable, but the former is more specific。您可以看到只有两种方法中的一种是这种情况,execute(() -> null); 将调用该方法。

为了证明 execute(Callable<Void>)execute(Supplier<T>) 更具体,我们必须转到 §18.5.4,因为后者是通用方法。

Let m1 be the first method and m2 be the second method. Where m2 has type parameters P1, ..., Pp, let α1, ..., αp be inference variables, and let θ be the substitution [P1:=α1, ..., Pp:=αp].

Let e1, ..., ek be the argument expressions of the corresponding invocation. Then:

  • If m1 and m2 are applicable by strict or loose invocation (§15.12.2.2, §15.12.2.3), then let S1, ..., Sk be the formal parameter types of m1, and let T1, ..., Tk be the result of θ applied to the formal parameter types of m2.
  • [...]

所以m1execute(Callable<Void>)m2execute(Supplier<T>)P1T。对于调用 execute(() -> null);e1() -> null,而 T 被推断为 Object,所以 α1ObjectT1 就是 Supplier<Object>S1Callable<Void>.

现在只引用与问题相关的部分:

The process to determine if m1 is more specific than m2 is as follows:

  • First, an initial bound set, B, is constructed from the declared bounds of P1, ..., Pp, as specified in §18.1.3.

  • Second, for all i (1 ≤ i ≤ k), a set of constraint formulas or bounds is generated.

    Otherwise, Ti is a parameterization of a functional interface, I. It must be determined whether Si satisfies the following five conditions:

    [...]

    If all five conditions are true, then the following constraint formulas or bounds are generated (where U1 ... Uk and R1 are the parameter types and return type of the function type of the capture of Si, and V1 ... Vk and R2 are the parameter types and return type of the function type of Ti):

    • If ei is an explicitly typed lambda expression:
      • [...]
      • Otherwise, ‹R1 <: R2›.

请注意,没有参数的 lambda 是显式类型的 lambda。

将此应用回您的问题,R1VoidR2Object,约束 ‹R1 <: R2› 表示 Void(不是小写的void)是Object的子类型,是正确的,并不矛盾。

最后:

Fourth, the generated bounds and constraint formulas are reduced and incorporated with B to produce a bound set B'.

If B' does not contain the bound false, and resolution of all the inference variables in B' succeeds, then m1 is more specific than m2.

由于约束‹Void <: Object›不矛盾,不存在false约束,所以execute(Callable<Void>)execute(Supplier<T>)更具体。


// Supplier -> this returns an Object, but how is that different from returning null?
execute(() -> new Object());

在这种情况下,只有Supplier<T>方法适用Callable<Void> 希望您 return 与 Void 兼容,而不是 Object


// Callable -> because it can throw an exception, right?
execute(() -> {throw new Exception();});

相当。抛出异常使 Callable<Void> 重载适用,但 Runnable 重载仍然适用。之所以选择前者,仍然是因为 Callable<Void> 对于表达式 () -> { throw new Exception(); }Runnable 更具体(仅相关部分):

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 U1 ... Uk and R1 are the parameter types and return type of the function type of the capture of S, and V1 ... Vk and R2 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:
    • R2 is void.

基本上,对于显式类型的 lambda,任何非 void-returning 功能接口类型都比 void-returning 功能接口类型更具体.