为什么此类型推断不适用于此Lambda表达式方案?
我有一个奇怪的场景,类型推断不能正常工作,因为我在使用lambda表达式时会发现。这是我的真实场景的近似值:
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:
static class Value<T> {
}
@FunctionalInterface
interface Bar<T> {
T apply(Value<T> value); // Change here resolves error
}
static class Foo {
public static <T> T foo(Bar<T> callback) {
}
}
void test() {
Foo.foo(value -> true).booleanValue(); // Compile error here
}
我在第二行到最后一行的编译错误是
The compile error I get on the second to last line is
方法booleanValue()未定义类型Object
The method booleanValue() is undefined for the type Object
如果我将lambda转换为 Bar< Boolean>
:
if I cast the lambda to Bar<Boolean>
:
Foo.foo((Bar<Boolean>)value -> true).booleanValue();
或者如果我更改 Bar.apply $ c的方法签名$ c>使用原始类型:
or if I change the method signature of Bar.apply
to use raw types:
T apply(Value value);
然后问题就消失了。我期望它的工作方式是:
then the problem goes away. The way I'd expect this to work is that:
-
Foo.foo
call应在lambda中推断返回类型boolean
-
value
推断为值< Boolean>
。
-
Foo.foo
call should infer a return type ofboolean
-
value
in the lambda should be inferred toValue<Boolean>
.
为什么此推理不能按预期工作?如何更改此API以使其按预期工作?
Why doesn't this inference work as expected and how can I change this API to make it work as expected?
在引擎盖下
使用一些隐藏的 javac
功能,我们可以获得有关正在发生的事情的更多信息:
Under the Hood
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: <none>
with type-args: no arguments
candidates:
#0 applicable method found: <T>foo(Bar<T>)
(partially instantiated to: (Bar<Object>)Object)
where T is a type-variable:
T extends Object declared in method <T>foo(Bar<T>)
LambdaInference.java:16: Note: Deferred instantiation of method <T>foo(Bar<T>)
Foo.foo(value -> true).booleanValue(); // Compile error here
^
instantiated signature: (Bar<Object>)Object
target-type: <none>
where T is a type-variable:
T extends Object declared in method <T>foo(Bar<T>)
LambdaInference.java:16: error: cannot find symbol
Foo.foo(value -> true).booleanValue(); // Compile error here
^
symbol: method booleanValue()
location: class Object
1 error
这是很多信息,让我们把它分解。
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: <none>
with type-args: no arguments
candidates:
#0 applicable method found: <T>foo(Bar<T>)
(partially instantiated to: (Bar<Object>)Object)
where T is a type-variable:
T extends Object declared in method <T>foo(Bar<T>)
阶段:方法适用性阶段
actuals:传递的实际参数
type-args:显式类型参数
候选人:适用的方法
实际值是< none>
因为我们隐式输入的lambda不是与适用性相关。
actuals is <none>
because our implicitly typed lambda is not pertinent to applicability.
编译器解析你的将 foo
调用到 Foo
中名为 foo
的唯一方法。它已被部分实例化为 Foo。< Object> foo
(因为没有实际值或类型参数),但是可以在延迟推理阶段改变。
The compiler resolves your invocation of foo
to the only method named foo
in Foo
. It has been partially instantiated to Foo.<Object> 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 <T>foo(Bar<T>)
Foo.foo(value -> true).booleanValue(); // Compile error here
^
instantiated signature: (Bar<Object>)Object
target-type: <none>
where T is a type-variable:
T extends Object declared in method <T>foo(Bar<T>)
实例化签名: foo
的完全实例化签名。这是此步骤的结果(此时不再对 foo
的签名进行类型推断。)
target-type:the正在进行调用的上下文。如果方法调用是赋值的一部分,则它将是左侧。如果方法调用本身是方法调用的一部分,那么它将是参数类型。
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.
由于方法调用是悬空的,因此没有目标类型。由于没有目标类型,因此无法在 foo
上进行更多推断,并且 T
被推断为对象
。
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
.
编译器在推理期间不使用隐式类型的lambda。在某种程度上,这是有道理的。通常,给定 param - > BODY
,在 param
的类型之前,您将无法编译 BODY
。如果您尝试从 BODY
推断 param
的类型,则可能会导致鸡蛋类型问题。在未来的Java版本中,可能会对此进行一些改进。
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.
Foo。< Boolean> foo(value - > true)
此解决方案为 foo $ c $提供显式类型参数c>(注意下面的
带有type-args
部分)。这会将方法签名的部分实例化更改为(Bar< Boolean>)布尔值
,这就是您想要的。
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>)Boolean
, which is what you want.
LambdaInference.java:16: Note: resolving method foo in type Foo to candidate 0
Foo.<Boolean> foo(value -> true).booleanValue(); // Compile error here
^
phase: BASIC
with actuals: <none>
with type-args: Boolean
candidates:
#0 applicable method found: <T>foo(Bar<T>)
(partially instantiated to: (Bar<Boolean>)Boolean)
where T is a type-variable:
T extends Object declared in method <T>foo(Bar<T>)
LambdaInference.java:16: Note: resolving method booleanValue in type Boolean to candidate 0
Foo.<Boolean> 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< Boolean> value) - > true)
此解决方案显式键入您的lambda,允许它与适用性相关(注意下面有实际值
)。这会将方法签名的部分实例化更改为(Bar< Boolean>)布尔值
,这就是您想要的。
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>)Boolean
, which is what you want.
LambdaInference.java:16: Note: resolving method foo in type Foo to candidate 0
Foo.foo((Value<Boolean> value) -> true).booleanValue(); // Compile error here
^
phase: BASIC
with actuals: Bar<Boolean>
with type-args: no arguments
candidates:
#0 applicable method found: <T>foo(Bar<T>)
(partially instantiated to: (Bar<Boolean>)Boolean)
where T is a type-variable:
T extends Object declared in method <T>foo(Bar<T>)
LambdaInference.java:16: Note: Deferred instantiation of method <T>foo(Bar<T>)
Foo.foo((Value<Boolean> value) -> true).booleanValue(); // Compile error here
^
instantiated signature: (Bar<Boolean>)Boolean
target-type: <none>
where T is a type-variable:
T extends Object declared in method <T>foo(Bar<T>)
LambdaInference.java:16: Note: resolving method booleanValue in type Boolean to candidate 0
Foo.foo((Value<Boolean> 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< Boolean>)值 - > 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<Boolean>) value -> true).booleanValue(); // Compile error here
^
phase: BASIC
with actuals: Bar<Boolean>
with type-args: no arguments
candidates:
#0 applicable method found: <T>foo(Bar<T>)
(partially instantiated to: (Bar<Boolean>)Boolean)
where T is a type-variable:
T extends Object declared in method <T>foo(Bar<T>)
LambdaInference.java:16: Note: Deferred instantiation of method <T>foo(Bar<T>)
Foo.foo((Bar<Boolean>) value -> true).booleanValue(); // Compile error here
^
instantiated signature: (Bar<Boolean>)Boolean
target-type: <none>
where T is a type-variable:
T extends Object declared in method <T>foo(Bar<T>)
LambdaInference.java:16: Note: resolving method booleanValue in type Boolean to candidate 0
Foo.foo((Bar<Boolean>) 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: <none>
with type-args: no arguments
candidates:
#0 applicable method found: <T>foo(Bar<T>)
(partially instantiated to: (Bar<Object>)Object)
where T is a type-variable:
T extends Object declared in method <T>foo(Bar<T>)
LambdaInference.java:16: Note: Deferred instantiation of method <T>foo(Bar<T>)
Boolean b = Foo.foo(value -> true);
^
instantiated signature: (Bar<Boolean>)Boolean
target-type: Boolean
where T is a type-variable:
T extends Object declared in method <T>foo(Bar<T>)
免责声明
这是正在发生的行为。我不知道这是否是JLS中指定的内容。我可以四处寻找,看看我是否能找到指定此行为的确切部分,但类型推断表示法令我头疼。
这也无法完全解释为什么要更改 Bar
使用原始值
将解决此问题:
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: <none>
with type-args: no arguments
candidates:
#0 applicable method found: <T>foo(Bar<T>)
(partially instantiated to: (Bar<Object>)Object)
where T is a type-variable:
T extends Object declared in method <T>foo(Bar<T>)
LambdaInference.java:16: Note: Deferred instantiation of method <T>foo(Bar<T>)
Foo.foo(value -> true).booleanValue();
^
instantiated signature: (Bar<Boolean>)Boolean
target-type: <none>
where T is a type-variable:
T extends Object declared in method <T>foo(Bar<T>)
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()
出于某种原因,将其更改为使用原始值
允许延迟实例化推断 T
是布尔值
。如果我不得不推测,我猜想当编译器试图将lambda拟合到 Bar< T>
时,它可以推断出 T 是
Boolean
。这意味着我之前的分析是不正确的。编译器可以在lambda的主体上执行类型推断,但仅限于仅在返回类型中出现的类型变量。
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<T>
, 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.