简体   繁体   English

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

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

First off, I have no idea how to decently phrase the question, so this is up for suggestions.首先,我不知道如何恰当地表达这个问题,所以这是征求意见。

Lets say we have following overloaded methods:假设我们有以下重载方法:

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();
}

Going off from this table, I got from another SO question离开这张桌子,我从另一个 SO question得到

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

These are the results I get locally:这些是我在本地得到的结果:

// 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();});

How does the compiler know which method to call?编译器如何知道调用哪个方法? How does it for example make the distinction between what's a Callable and what's a Runnable ?例如,它如何区分什么是Callable和什么是Runnable

I believe I have found where this is described in official documentation, although a bit hard to read.我相信我已经找到了官方文档中对此进行描述的位置,尽管有点难以阅读。

Here is mentioned: 这里提到:

15.27.3. 15.27.3。 Type of a Lambda Expression Lambda 表达式的类型

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

and then here (15.12.2.5) is described analytically how the most specific method is chosen.然后这里 (15.12.2.5)分析地描述了如何选择最具体的方法。

So according to this for example as described所以根据这个例如描述

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:一个适用的方法 m1 比另一个适用的方法 m2 更具体,对于带有参数表达式 e1, ..., ek 的调用,如果以下任何一项为真:

m2 is generic, and m1 is inferred to be more specific than m2 for argument expressions e1, ..., ek m2 是通用的,对于参数表达式 e1, ..., ek,m1 被推断为比 m2 更具体

So所以

// 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();
 }

Why M1 is inferred to be more specific can be traced down from this process described here (18.5.4 More Specific Method Inference)为什么 M1 被推断为更具体可以从这里描述的这个过程中追溯(18.5.4 更具体的方法推断)

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

This is because both the Callable<Void> method and the Supplier<T> method are applicable , but the former is more specific .这是因为Callable<Void>方法和Supplier<T>方法都适用,但前者更具体 You can see that this is the case by having only one of the two methods, and execute(() -> null);您可以看到只有两种方法之一和execute(() -> null);就是这种情况。 will call that method.将调用该方法。

To show that execute(Callable<Void>) is more specific than execute(Supplier<T>) , we'll have to go to §18.5.4 , since the latter is a generic method.为了表明execute(Callable<Void>)execute(Supplier<T>)更具体,我们必须从 go 到§18.5.4 ,因为后者是通用方法。

Let m1 be the first method and m2 be the second method.令 m1 为第一种方法,m2 为第二种方法。 Where m2 has type parameters P1, ..., Pp, let α1, ..., αp be inference variables, and let θ be the substitution [P1:=α1, ..., Pp:=αp].其中 m2 具有类型参数 P1, ..., Pp,令 α1, ..., αp 为推理变量,令 θ 为替代 [P1:=α1, ..., Pp:=αp]。

Let e1, ..., ek be the argument expressions of the corresponding invocation.令 e1, ..., ek 为相应调用的参数表达式。 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.如果 m1 和 m2 可通过严格或松散调用(§15.12.2.2、§15.12.2.3)应用,则令 S1、...、Sk 为 m1 的形式参数类型,并令 T1、...、Tk 为θ 应用于 m2 形式参数类型的结果。
  • [...] [...]

So m1 is execute(Callable<Void>) , and m2 is execute(Supplier<T>) .所以m1execute(Callable<Void>)m2execute(Supplier<T>) P1 is T . P1T For the invocation execute(() -> null);对于调用execute(() -> null); , e1 is () -> null , and T is inferred to be Object , so α1 is Object . , e1() -> null , T推断为Object , 所以α1Object T1 is then Supplier<Object> . T1然后是Supplier<Object> S1 is Callable<Void> . S1Callable<Void>

Now quoting only the parts relevant to the question:现在只引用与问题相关的部分:

The process to determine if m1 is more specific than m2 is as follows:确定 m1 是否比 m2 更具体的过程如下:

  • First, an initial bound set, B, is constructed from the declared bounds of P1, ..., Pp, as specified in §18.1.3.首先,初始边界集 B 是根据声明的边界 P1, ..., Pp 构建的,如 §18.1.3 中所述。

  • Second, for all i (1 ≤ i ≤ k), a set of constraint formulas or bounds is generated.其次,对于所有的 i (1 ≤ i ≤ k),生成一组约束公式或边界。

    Otherwise, Ti is a parameterization of a functional interface, I. It must be determined whether Si satisfies the following five conditions:否则,Ti 是函数接口 I 的参数化。必须确定 Si 是否满足以下五个条件:

    [...] [...]

    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):如果五个条件都为真,则生成如下约束公式或边界(其中U1...Uk和R1为捕获Si的function类型的参数类型和返回类型,V1...Vk和R2是Ti的function类型的参数类型和返回类型):

    • If ei is an explicitly typed lambda expression:如果 ei 是显式类型化的 lambda 表达式:
      • [...] [...]
      • Otherwise, ‹R1 <: R2›.否则,‹R1 <: R2›。

Note that a lambda with no parameters is an explicitly typed lambda.请注意,没有参数的 lambda 是显式类型的 lambda。

Applying this back to your question, R1 is Void , R2 is Object , and the constraint ‹R1 <: R2› says that Void (not the lowercase void ) is a subtype of Object , which is correct and is not contradictory.将此应用回您的问题, R1VoidR2Object ,并且约束‹R1 <: R2›表示Void (不是小写的void )是Object的子类型,这是正确的并且并不矛盾。

Finally:最后:

Fourth, the generated bounds and constraint formulas are reduced and incorporated with B to produce a bound set B'.第四,将生成的边界和约束公式简化并与B合并以产生边界集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.如果 B' 不包含绑定 false,并且 B' 中所有推理变量的解析成功,则 m1 比 m2 更具体。

Since the constraint ‹Void <: Object› is not contradictory, there is no false constraint, and so execute(Callable<Void>) is more specific than execute(Supplier<T>) .由于约束‹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());

In this case, only the Supplier<T> method is applicable .在这种情况下,只有Supplier<T>方法适用 Callable<Void> expects you to return something compatible with Void , not Object . Callable<Void>期望您返回与Void兼容的内容,而不是Object


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

Not quite .完全是 Throwing an exception made the Callable<Void> overload applicable, but the Runnable overload is still applicable too.抛出异常使Callable<Void>重载适用,但Runnable重载仍然适用。 The reason why the former is chosen is still because Callable<Void> is more specific than Runnable for the expression () -> { throw new Exception(); }之所以选择前者,还是因为Callable<Void>对于表达式() -> { throw new Exception(); }Runnable更具体。 () -> { throw new Exception(); } (relevant parts only): () -> { throw new Exception(); } (仅相关部分):

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):如果 T 不是 S 的子类型并且以下条件之一为真(其中 U1...Uk 和 R1 是参数类型和返回类型),则功能接口类型 S 比表达式 e 的功能接口类型 T 更具体S的捕获的function类型,V1...Vk和R2是T的function类型的参数类型和返回类型:

  • If e is an explicitly typed lambda expression (§15.27.1), then one of the following is true:如果 e 是显式类型化的 lambda 表达式 (§15.27.1),则以下条件之一为真:
    • R2 is void . R2 void

Basically, any non- void -returning functional interface type is more specific than a void -returning functional interface type, for explicitly typed lambdas.基本上,对于显式类型的 lambda,任何非返回void的函数接口类型都比返回void的函数接口类型更具体。

It all makes sense and has a simple pattern besides () -> null being a Callable I think.这一切都是有道理的,并且除了() -> null之外还有一个简单的模式,我认为它是一个Callable The Runnable is clearly different from the Supplier / Callable as it has no input and output values. Runnable明显不同于Supplier / Callable ,因为它没有输入和 output 值。 The difference between Callable and Supplier is that with the Callable you have to handle exceptions. CallableSupplier之间的区别在于,对于Callable ,您必须处理异常。

The reason that () -> null is a Callable without an exception is the return type of your definition Callable<Void> . () -> null是 Callable 无一例外的原因是您定义的返回类型Callable<Void> It requires you to return the reference to some object. The only possible reference to return for Void is null .它要求您返回对某些 object 的引用。返回Void的唯一可能引用是null This means that the lambda () -> null is exactly what your definition demands.这意味着 lambda () -> null正是您的定义所要求的。 It would also work for your Supplier example if you would remove the Callable definition.如果您删除Callable定义,它也适用于您的Supplier示例。 However, it uses Callable<Void> over Supplier<T> as the Callable has the exact type.但是,它使用Callable<Void>而不是Supplier<T> ,因为Callable具有确切的类型。

Callable is chosen over Supplier as it is more specific (as a comment already suggested).选择Callable而不是Supplier因为它更具体(正如已经建议的评论)。 The Java Docs state that it chooses the most specific type if possible: Java Docs state 它尽可能选择最具体的类型:

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.类型推断是 Java 编译器查看每个方法调用和相应声明以确定使调用适用的类型参数(或参数)的能力。 The inference algorithm determines the types of the arguments and, if available, the type that the result is being assigned, or returned.推理算法确定 arguments 的类型,如果可用,则确定分配或返回结果的类型。 Finally, the inference algorithm tries to find the most specific type that works with all of the arguments.最后,推理算法尝试找到适用于所有 arguments 的最具体的类型。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何在 java 中为供应商或消费者引用私有方法 - How to refer a private method for a Supplier or Consumer in java Java,编译器如何知道在这个 lambda 表达式中调用哪个构造函数 - Java, How does the compiler know which constructor to call in this lambda expression 如何在Java中的CompletionService中知道哪个可调用完成池方法 - How to know which callable finished upon pool method in CompletionService in java 如何在Java中调用重载方法? - how to call overloaded method in java? Java如何选择要调用的重载函数? - How does Java pick which overloaded function to call? java 如何确定,当一个方法具有泛型类型参数而另一个具有非泛型参数时,将调用哪个重载方法? - How does java determine, which overloaded method will call, when one method with generic type parameter and other with non generic parameter? 如何在Java 8中使用消费者和供应商来代替反射 - How to use consumer and supplier instead Reflection in java 8 使用lambda表达式和Supplier / Consumer时出现java.lang.ClassCastException - java.lang.ClassCastException while using the lambda expression and Supplier/Consumer java如何区分Lambda中的Callable和Runnable? - How does java differentiate Callable and Runnable in a Lambda? Java供应商/消费者/可运行的? 作为字符串参数提供时如何调用方法 - Java Supplier/Consumer/Runnable? How to invoke method when it is supplied as a a string argument
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM