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

Edw*_*ard 23 java lambda overloading function

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

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

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();
}
Run Code Online (Sandbox Code Playgroud)

从这张表出发,我从另一个问题中得到了答案

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
Run Code Online (Sandbox Code Playgroud)

以下是我在本地得到的结果:

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
Run Code Online (Sandbox Code Playgroud)

编译器如何知道要调用哪个方法?例如,它如何区分什么是 aCallable和什么是 a Runnable

Pan*_*kos 17

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

\n

这里提到:

\n
\n

15.27.3。Lambda 表达式的类型

\n

请注意,虽然严格调用上下文中不允许装箱,但始终允许对 lambda 结果表达式进行装箱 - 也就是说,结果表达式出现在赋值上下文中,而不管包含 lambda 表达式的上下文如何。但是,如果显式\n类型化 lambda 表达式是重载方法的参数,\n最具体的检查 (\xc2\xa715.12.2.5) 会首选避免装箱或拆箱 lambda 结果的方法签名。

\n
\n

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

\n

所以根据这个例如所描述的

\n
\n

对于使用参数表达式 e1, ..., ek 的调用,如果满足\n以下任一条件,则一种适用的方法 m1 比另一种适用的方法 m2 更具体:

\n

m2 是通用的,对于参数表达式 e1, ..., ek,m1 被推断为比 m2 更具体

\n
\n

所以

\n
// Callable -> why is it not a Supplier?\nexecute(() -> null);   <-- Callable shall be picked from 2 options as M2 is generic and M1 is inferred to be more specific\n\nvoid execute(Callable<Void> callable) {  // <------ M1 \n   try {\n    callable.call();\n  } catch (Exception e) {\n      e.printStackTrace();\n  }\n}\n\n\n <T> T execute(Supplier<T> supplier) {  // <------ M2 is Generic\n    return supplier.get();\n }\n
Run Code Online (Sandbox Code Playgroud)\n

为什么 M1 被推断得更具体可以从这里描述的这个过程中追溯到(18.5.4 更具体的方法推断)

\n


Swe*_*per 7

// Callable -> why is it not a Supplier? It does not throw any exceptions..\nexecute(() -> null);\n
Run Code Online (Sandbox Code Playgroud)\n

这是因为Callable<Void>方法和Supplier<T>方法都适用,但前者更具体。您可以看到,只有两种方法之一就是这种情况,并且execute(() -> null);将调用该方法。

\n

为了表明它execute(Callable<Void>)比 更具体execute(Supplier<T>),我们必须转到\xc2\xa718.5.4,因为后者是通用方法。

\n
\n

设 m1 为第一种方法,m2 为第二种方法。其中 m2 具有类型参数 P1, ..., Pp,令 \xce\xb11, ..., \xce\xb1p 为推理变量,并令 \xce\xb8 为替换 [P1:=\xce\xb11, . .., Pp:=\xce\xb1p]。

\n

令 e1, ..., ek 为相应调用的参数表达式。然后:

\n
    \n
  • 如果 m1 和 m2 可通过严格或松散调用应用(\xc2\xa715.12.2.2, \xc2\xa715.12.2.3),则令 S1, ..., Sk 为 m1 的形式参数类型,并令T1, ..., Tk 是 \xce\xb8 应用于 m2 的形式参数类型的结果。
  • \n
  • [...]
  • \n
\n
\n

如此,m1也是如此。是。对于调用,is ,并且被推断为 is ,所以is 。就是那么。是execute(Callable<Void>)m2execute(Supplier<T>)P1Texecute(() -> null);e1() -> nullTObject\xce\xb11ObjectT1Supplier<Object>S1Callable<Void>

\n

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

\n
\n

判断m1是否比m2更具体的过程如下:

\n
    \n
  • 首先,根据 \xc2\xa718.1.3 中指定的 P1, ..., Pp 的声明边界构造初始边界集 B。

    \n
  • \n
  • 其次,对于所有 i (1 \xe2\x89\xa4 i \xe2\x89\xa4 k),生成一组约束公式或边界。

    \n

    否则,Ti 是功能接口 I 的参数化。必须确定 Si 是否满足以下五个条件:

    \n

    [...]

    \n

    如果五个条件全部成立,则生成以下约束公式或边界(其中 U1 ... Uk 和 R1 是捕获 Si 的函数类型的参数类型和返回类型,V1 ... Vk 和 R2分别是Ti的函数类型的参数类型和返回类型):

    \n
      \n
    • 如果 ei 是显式类型化 lambda 表达式:\n
        \n
      • [...]
      • \n
      • 否则,\xe2\x80\xb9R1 <: R2\xe2\x80\xba。
      • \n
      \n
    • \n
    \n
  • \n
\n
\n

请注意,不带参数的 lambda 是显式类型化 lambda。

\n

将其应用回您的问题R1is VoidR2is Object,并且约束\xe2\x80\xb9R1 <: R2\xe2\x80\xba表示Void(不是小写void)是 的子类型Object,这是正确的且不矛盾。

\n

最后:

\n
\n

第四,将生成的边界和约束公式减少并与 B 合并以产生边界集 B'。

\n

如果 B\' 不包含绑定 false,并且 B\' 中的所有推理变量解析成功,则 m1 比 m2 更具体。

\n
\n

由于约束\xe2\x80\xb9Void <: Object\xe2\x80\xba并不矛盾,因此没有false约束,因此execute(Callable<Void>)比 更具体execute(Supplier<T>)

\n
\n
// Supplier -> this returns an Object, but how is that different from returning null?\nexecute(() -> new Object());\n
Run Code Online (Sandbox Code Playgroud)\n

在这种情况下,就只能采用Supplier<T>该方法了。期望您返回与 兼容的东西,而不是。Callable<Void>VoidObject

\n
\n
// Callable -> because it can throw an exception, right?\nexecute(() -> {throw new Exception();});\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
\n

对于表达式 e,如果 T 不是 S 的子类型并且以下条件之一为真,则函数接口类型 S 比函数接口类型 T 更具体(其中 U1 ... Uk 和 R1 是S捕获的函数类型,V1...Vk和R2是T的函数类型的参数类型和返回类型):

\n
    \n
  • 如果 e 是显式类型化 lambda 表达式 (\xc2\xa715.27.1),则以下条件之一为 true:\n
      \n
    • R2 是void
    • \n
    \n
  • \n
\n
\n

基本上,对于显式类型化的 lambda,任何非void返回函数接口类型都比void返回函数接口类型更具体。

\n