I have a weird scenario where type inference isn\'t working as I\'d expect when using a lambda expression. Here\'s an approximation of my real scenario:
Using some hidden javac features, we can get more information about what's happening:
$ javac -XDverboseResolution=deferred-inference,success,applicable LambdaInference.java
LambdaInference.java:16: Note: resolving method foo in type Foo to candidate 0
Foo.foo(value -> true).booleanValue(); // Compile error here
^
phase: BASIC
with actuals:
with type-args: no arguments
candidates:
#0 applicable method found: foo(Bar)
(partially instantiated to: (Bar
This is a lot of information, let's break it down.
LambdaInference.java:16: Note: resolving method foo in type Foo to candidate 0
Foo.foo(value -> true).booleanValue(); // Compile error here
^
phase: BASIC
with actuals:
with type-args: no arguments
candidates:
#0 applicable method found: foo(Bar)
(partially instantiated to: (Bar)Object)
where T is a type-variable:
T extends Object declared in method foo(Bar)
phase: method applicability phase
actuals: the actual arguments passed in
type-args: explicit type arguments
candidates: potentially applicable methods
actuals is because our implicitly typed lambda is not pertinent to applicability.
The compiler resolves your invocation of foo to the only method named foo in Foo. It has been partially instantiated to Foo. foo (since there were no actuals or type-args), but that can change at the deferred-inference stage.
LambdaInference.java:16: Note: Deferred instantiation of method foo(Bar)
Foo.foo(value -> true).booleanValue(); // Compile error here
^
instantiated signature: (Bar)Object
target-type:
where T is a type-variable:
T extends Object declared in method foo(Bar)
instantiated signature: the fully instantiated signature of foo. It is the result of this step (at this point no more type inference will be made on the signature of foo).
target-type: the context the call is being made in. If the method invocation is a part of an assignment, it will be the left hand side. If the method invocation is itself part of a method invocation, it will be the parameter type.
Since your method invocation is dangling, there is no target-type. Since there is no target-type, no more inference can be done on foo and T is inferred to be Object.
Analysis
The compiler does not use implicitly typed lambdas during inference. To a certain extent, this makes sense. In general, given param -> BODY, you will not be able to compile BODY until you have a type for param. If you did try to infer the type for param from BODY, it might lead to a chicken-and-egg type problem. It's possible that some improvements will be made on this in future releases of Java.
Solutions
Foo. foo(value -> true)
This solution provides an explicit type argument to foo (note the with type-args section below). This changes the partial instantiation of the method signature to (Bar)Boolean, which is what you want.
LambdaInference.java:16: Note: resolving method foo in type Foo to candidate 0
Foo. foo(value -> true).booleanValue(); // Compile error here
^
phase: BASIC
with actuals:
with type-args: Boolean
candidates:
#0 applicable method found: foo(Bar)
(partially instantiated to: (Bar)Boolean)
where T is a type-variable:
T extends Object declared in method foo(Bar)
LambdaInference.java:16: Note: resolving method booleanValue in type Boolean to candidate 0
Foo. foo(value -> true).booleanValue(); // Compile error here
^
phase: BASIC
with actuals: no arguments
with type-args: no arguments
candidates:
#0 applicable method found: booleanValue()
Foo.foo((Value value) -> true)
This solution explicitly types your lambda, which allows it to be pertinent to applicability (note with actuals below). This changes the partial instantiation of the method signature to (Bar)Boolean, which is what you want.
LambdaInference.java:16: Note: resolving method foo in type Foo to candidate 0
Foo.foo((Value value) -> true).booleanValue(); // Compile error here
^
phase: BASIC
with actuals: Bar
with type-args: no arguments
candidates:
#0 applicable method found: foo(Bar)
(partially instantiated to: (Bar)Boolean)
where T is a type-variable:
T extends Object declared in method foo(Bar)
LambdaInference.java:16: Note: Deferred instantiation of method foo(Bar)
Foo.foo((Value value) -> true).booleanValue(); // Compile error here
^
instantiated signature: (Bar)Boolean
target-type:
where T is a type-variable:
T extends Object declared in method foo(Bar)
LambdaInference.java:16: Note: resolving method booleanValue in type Boolean to candidate 0
Foo.foo((Value value) -> true).booleanValue(); // Compile error here
^
phase: BASIC
with actuals: no arguments
with type-args: no arguments
candidates:
#0 applicable method found: booleanValue()
Foo.foo((Bar) value -> true)
Same as above, but with a slightly different flavor.
LambdaInference.java:16: Note: resolving method foo in type Foo to candidate 0
Foo.foo((Bar) value -> true).booleanValue(); // Compile error here
^
phase: BASIC
with actuals: Bar
with type-args: no arguments
candidates:
#0 applicable method found: foo(Bar)
(partially instantiated to: (Bar)Boolean)
where T is a type-variable:
T extends Object declared in method foo(Bar)
LambdaInference.java:16: Note: Deferred instantiation of method foo(Bar)
Foo.foo((Bar) value -> true).booleanValue(); // Compile error here
^
instantiated signature: (Bar)Boolean
target-type:
where T is a type-variable:
T extends Object declared in method foo(Bar)
LambdaInference.java:16: Note: resolving method booleanValue in type Boolean to candidate 0
Foo.foo((Bar) value -> true).booleanValue(); // Compile error here
^
phase: BASIC
with actuals: no arguments
with type-args: no arguments
candidates:
#0 applicable method found: booleanValue()
Boolean b = Foo.foo(value -> true)
This solution provides an explicit target for your method call (see target-type below). This allows the deferred-instantiation to infer that the type parameter should be Boolean instead of Object (see instantiated signature below).
LambdaInference.java:16: Note: resolving method foo in type Foo to candidate 0
Boolean b = Foo.foo(value -> true);
^
phase: BASIC
with actuals:
with type-args: no arguments
candidates:
#0 applicable method found: foo(Bar)
(partially instantiated to: (Bar)Object)
where T is a type-variable:
T extends Object declared in method foo(Bar)
LambdaInference.java:16: Note: Deferred instantiation of method foo(Bar)
Boolean b = Foo.foo(value -> true);
^
instantiated signature: (Bar)Boolean
target-type: Boolean
where T is a type-variable:
T extends Object declared in method foo(Bar)
Disclaimer
This is the behavior that's occurring. I don't know if this is what is specified in the JLS. I could dig around and see if I could find the exact section that specifies this behavior, but type inference notation gives me a headache.
This also doesn't fully explain why changing Bar to use a raw Value would fix this issue:
LambdaInference.java:16: Note: resolving method foo in type Foo to candidate 0
Foo.foo(value -> true).booleanValue();
^
phase: BASIC
with actuals:
with type-args: no arguments
candidates:
#0 applicable method found: foo(Bar)
(partially instantiated to: (Bar)Object)
where T is a type-variable:
T extends Object declared in method foo(Bar)
LambdaInference.java:16: Note: Deferred instantiation of method foo(Bar)
Foo.foo(value -> true).booleanValue();
^
instantiated signature: (Bar)Boolean
target-type:
where T is a type-variable:
T extends Object declared in method foo(Bar)
LambdaInference.java:16: Note: resolving method booleanValue in type Boolean to candidate 0
Foo.foo(value -> true).booleanValue();
^
phase: BASIC
with actuals: no arguments
with type-args: no arguments
candidates:
#0 applicable method found: booleanValue()
For some reason, changing it to use a raw Value allows the deferred instantiation to infer that T is Boolean. If I had to speculate, I would guess that when the compiler tries to fit the lambda to the Bar, it can infer that T is Boolean by looking at the body of the lambda. This implies that my earlier analysis is incorrect. The compiler can perform type inference on the body of a lambda, but only on type variables that only appear in the return type.