[英]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.我相信我已经找到了官方文档中对此进行描述的位置,尽管有点难以阅读。
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>)
.所以m1
是execute(Callable<Void>)
, m2
是execute(Supplier<T>)
。 P1
is T
. P1
是T
。 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
, 所以α1
为Object
。 T1
is then Supplier<Object>
. T1
然后是Supplier<Object>
。 S1
is Callable<Void>
. S1
是Callable<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.将此应用回您的问题, R1
是Void
, R2
是Object
,并且约束‹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
. R2void
。
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. Callable
和Supplier
之间的区别在于,对于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.