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
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
?
CodePudding user response:
I believe I have found where this is described in official documentation, although a bit hard to read.
Here is mentioned:
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).
and then here (15.12.2.5) is described analytically how the most specific method is chosen.
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:
m2 is generic, and m1 is inferred to be more specific than m2 for argument expressions e1, ..., ek
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)
CodePudding user response:
It all makes sense and has a pattern besides () -> null
being a Callable
I think. The Runnable
is clearly different from the Supplier
/Callable
as it has no input and output values. The difference between Callable
and Supplier
is that with the Callable
you have to handle exceptions.
The reason that () -> null
is a Callable without an exception is the return type of your definition Callable<Void>
. It requires you to return the reference to some object. The only possible reference to return for Void
is null
. This means that the lambda () -> null
is exactly what your definition demands. It would also work for your Supplier
example if you would remove the Callable
definition. However, it uses Callable<Void>
over Supplier<T>
as the Callable
has the exact type.
Callable
is chosen over Supplier
as it is more specific (as a comment already suggested). The Java Docs state that it chooses the most specific type if possible:
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.
CodePudding user response:
// 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. You can see that this is the case by having only one of the two methods, and 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.
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.
- [...]
So m1
is execute(Callable<Void>)
, and m2
is execute(Supplier<T>)
. P1
is T
. For the invocation execute(() -> null);
, e1
is () -> null
, and T
is inferred to be Object
, so α1
is Object
. T1
is then Supplier<Object>
. S1
is 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:
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›.
Note that a lambda with no parameters is an explicitly typed 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.
Finally:
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.
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>)
.
// 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. Callable<Void>
expects you to return something compatible with Void
, not 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. The reason why the former is chosen is still because Callable<Void>
is more specific than Runnable
for the expression () -> { throw new Exception(); }
(relevant parts only):
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
.
Basically, any non-void
-returning functional interface type is more specific than a void
-returning functional interface type, for explicitly typed lambdas.