我有一个奇怪的场景,在使用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 }
我倒数第二行的编译错误是
未为对象类型定义方法booleanValue()
如果我将lambda转换为Bar<Boolean>:
Bar<Boolean>
Foo.foo((Bar<Boolean>)value -> true).booleanValue();
或者如果我将方法签名更改Bar.apply为使用原始类型:
Bar.apply
T apply(Value value);
然后问题就解决了。我希望它能起作用的方式是:
Foo.foo
boolean
value
Value<Boolean>
为什么这种推论不能按预期方式工作?如何更改此API以使其按预期方式工作?
使用一些隐藏的javac功能,我们可以获得有关正在发生的事情的更多信息:
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>)
阶段:方法适用性阶段 实际情况:在 类型参数中传递的实际参数:显式类型参数 候选项:可能适用的方法
实际情况是<none>因为我们的隐式lambda 与适用性无关。
<none>
编译器解决您的调用foo来命名的唯一方法foo在Foo。它已经部分实例化到Foo.<Object> foo(因为没有实际值或type- args),但是可以在推论阶段改变。
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)。 target-type:在其中进行调用的上下文。如果方法调用是分配的一部分,则它将在左侧。如果方法调用本身是方法调用的一部分,则它将是参数类型。
由于您的方法调用是悬空的,因此没有目标类型。由于没有目标类型,因此无法进行进一步的推断,foo并T推断为Object。
T
Object
编译器在推理期间不使用隐式类型的lambda。在某种程度上,这是有道理的。通常,给定的类型param -> BODY是您将无法编译。如果您确实尝试推断from 的类型,则可能会导致鸡和蛋的类型出现问题。在将来的Java版本中,可能对此进行一些改进。BODY``param``param``BODY
param -> BODY
BODY``param``param``BODY
Foo.<Boolean> foo(value -> true)
此解决方案提供了一个显式类型参数foo(请注意以下with type- args部分)。这会将方法签名的部分实例化更改为(Bar<Boolean>)Boolean,这就是您想要的。
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,这就是您想要的。
with actuals
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下文)。这使deferred- instantiation可以推断应该使用type参数Boolean代替Object(请参见instantiated signature下文)。
target-type
Boolean
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中指定的内容。我可以四处挖掘,看看是否可以找到指定此行为的确切部分,但是类型推断符号使我头疼。
这也不能完全解释为什么更改Bar为使用原始格式Value可以解决此问题:
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>,它可以通过查看lambda的主体来推断出这T一点Boolean。这意味着我先前的分析是不正确的。编译器 可以 对lambda主体执行类型推断,但只能对 仅 出现在返回类型中的类型变量执行。
Bar<T>