我有一个奇怪的场景,在使用 lambda 表达式时,类型推断无法正常工作。这是我真实情况的近似值:
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 method booleanValue() is undefined for the type Object
如果我将 lambda 转换为 Bar<Boolean>
:
Foo.foo((Bar<Boolean>)value -> true).booleanValue();
或者如果我更改 Bar.apply
的方法签名使用原始类型:
T apply(Value value);
然后问题就消失了。我希望它起作用的方式是:
-
Foo.foo
调用应该推断返回类型boolean
-
value
在 lambda 中应推断为Value<Boolean>
.
为什么这个推断没有按预期工作,我该如何更改这个 API 以使其按预期工作?
最佳答案
幕后
使用一些隐藏的javac
功能,我们可以获得有关正在发生的事情的更多信息:
$ 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
这是很多信息,让我们分解一下。
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>)
阶段:method applicability phase
actuals:传入的实际参数
type-args:显式类型参数
候选人:potentially applicable methods
实际值为 <none>
因为我们的隐式类型 lambda 不是 pertinent to applicability .
编译器会解析您对 foo
的调用到唯一名为 foo
的方法在 Foo
.它已部分实例化为 Foo.<Object> foo
(因为没有实际值或类型参数),但这可能会在延迟推理阶段发生变化。
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
的签名进行类型推断)。
目标类型:调用的上下文。如果方法调用是赋值的一部分,它将在左侧。如果方法调用本身是方法调用的一部分,则它将是参数类型。
由于您的方法调用是悬空的,因此没有目标类型。由于没有目标类型,因此无法对 foo
进行更多推断。和 T
推断为Object
.
分析
编译器在推理过程中不使用隐式类型的 lambda。在某种程度上,这是有道理的。一般来说,给定 param -> BODY
,您将无法编译BODY
直到你有 param
的类型.如果您确实尝试推断 param
的类型来自 BODY
,它可能会导致鸡和蛋类型的问题。在 Java 的 future 版本中可能会对此进行一些改进。
解决方案
Foo.<Boolean> foo(value -> true)
此解决方案为 foo
提供显式类型参数(注意下面的 with type-args
部分)。这会将方法签名的部分实例化更改为 (Bar<Boolean>)Boolean
,这就是你想要的。
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,这使其与适用性相关(注释 with actuals
下面)。这会将方法签名的部分实例化更改为 (Bar<Boolean>)Boolean
,这就是你想要的。
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>) value -> true)
与上面相同,但味道略有不同。
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)
此解决方案为您的方法调用提供了一个明确的目标(见下文target-type
)。这允许延迟实例化推断类型参数应该是 Boolean
而不是 Object
(见下文 instantiated signature
)。
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 中指定的内容。我可以四处寻找,看看是否能找到指定此行为的确切部分,但是 type inference符号让我头疼。
这也不能完全解释为什么要更改 Bar
使用原始 Value
会解决这个问题:
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()
出于某种原因,将其更改为使用原始 Value
允许延迟实例化推断 T
是 Boolean
.如果我不得不推测,我猜当编译器试图将 lambda 拟合到 Bar<T>
,可以推断出T
是 Boolean
通过查看 lambda 的主体。这意味着我之前的分析是不正确的。编译器可以对 lambda 的主体执行类型推断,但仅限于仅出现在返回类型中的类型变量。
关于java - 为什么这种类型推断不适用于这个 Lambda 表达式场景?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31227149/