Jos*_*one 38 java lambda type-inference java-8
我有一个奇怪的场景,类型推断不起作用,因为我期望使用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
}
Run Code Online (Sandbox Code Playgroud)
我在倒数第二行得到的编译错误是
对于Object类型,方法booleanValue()未定义
如果我将lambda转换为Bar<Boolean>:
Foo.foo((Bar<Boolean>)value -> true).booleanValue();
Run Code Online (Sandbox Code Playgroud)
或者如果我更改方法签名Bar.apply以使用原始类型:
T apply(Value value);
Run Code Online (Sandbox Code Playgroud)
然后问题就消失了.我期望它的工作方式是:
Foo.foo call应该推断出一个返回类型 boolean value在lambda中应该推断出来Value<Boolean>.为什么这个推理不能按预期工作?如何更改此API以使其按预期工作?
Jef*_*rey 31
使用一些隐藏的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
Run Code Online (Sandbox Code Playgroud)
这是很多信息,让我们分解一下.
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>)
Run Code Online (Sandbox Code Playgroud)
阶段:方法适用性阶段
实际:在
type-args中传递的实际参数:显式类型参数
候选:可能适用的方法
实际值是<none>因为我们的隐式类型lambda 与适用性无关.
编译器解决您的调用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>)
Run Code Online (Sandbox Code Playgroud)
实例化签名:完全实例化的签名foo.这是此步骤的结果(此时不再对签名进行类型推断foo).
target-type:正在进行调用的上下文.如果方法调用是赋值的一部分,则它将是左侧.如果方法调用本身是方法调用的一部分,那么它将是参数类型.
由于您的方法调用是悬空的,因此没有目标类型.由于没有目标类型,因此不能进行更多推断foo并T推断出Object.
编译器在推理期间不使用隐式类型的lambdas.在某种程度上,这是有道理的.一般来说,给定param -> BODY,BODY在你有一个类型之前你将无法编译param.如果您确实尝试推断出param来自的类型BODY,则可能导致鸡和蛋类型问题.在将来的Java版本中,可能会对此进行一些改进.
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()
Run Code Online (Sandbox Code Playgroud)
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()
Run Code Online (Sandbox Code Playgroud)
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()
Run Code Online (Sandbox Code Playgroud)
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>)
Run Code Online (Sandbox Code Playgroud)
这是正在发生的行为.我不知道这是否是JLS中指定的内容.我可以四处搜索,看看我是否能找到指定此行为的确切部分,但类型推断符号让我头疼.
这也无法完全解释为什么更改Bar使用raw 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()
Run Code Online (Sandbox Code Playgroud)
由于某种原因,将其更改为使用原始Value允许延迟实例化推断它T是Boolean.如果我不得不猜测,我猜想,当编译器试图以适应lambda来的Bar<T>,它可以推断,T是Boolean通过看拉姆达的身体.这意味着我之前的分析是不正确的.编译器可以对lambda的主体执行类型推断,但仅限于仅出现在返回类型中的类型变量.
对lambda参数类型的推断不能依赖于lambda体.
编译器面临着一个艰难的工作,试图理解隐含的lambda表达式
foo( value -> GIBBERISH )
Run Code Online (Sandbox Code Playgroud)
value在编译GIBBERISH之前必须首先推断出类型,因为一般来说,GIBBERISH的解释取决于GIBBERISH的定义value.
(在你的特殊情况下,GIBBERISH恰好是一个独立的简单常量value.)
Javac必须首先推断Value<T>参数value; 因此,在上下文中没有约束T=Object.然后,lambda body true被编译并识别为Boolean,与之兼容T.
在对功能接口进行更改后,lambda参数类型不需要推理; T仍未被提及.接下来,编译lambda主体,返回类型显示为Boolean,将其设置为下限T.
另一个证明问题的例子
<T> void foo(T v, Function<T,T> f) { ... }
foo("", v->42); // Error. why can't javac infer T=Object ?
Run Code Online (Sandbox Code Playgroud)
T被推断为String; lambda的身体没有参与推论.
在这个例子中,javac的行为对我们来说似乎非常合理; 它可能会阻止编程错误.你不希望推论过于强大; 如果我们编写的所有内容都以某种方式编译,我们将失去对编译器为我们发现错误的信心.
还有其他示例,其中lambda主体似乎提供明确的约束,但编译器不能使用该信息.在Java中,必须首先修复lambda参数类型,然后才能查看正文.这是一个深思熟虑的决定.相反,C#愿意尝试不同的参数类型,看看哪个使代码编译.Java认为风险太大.
在任何情况下,当隐式lambda失败时,这种情况经常发生,为lambda参数提供显式类型; 在你的情况下,(Value<Boolean> value)->true
| 归档时间: |
|
| 查看次数: |
6697 次 |
| 最近记录: |