为什么此类型推断不适用于此Lambda表达式方案? [英] Why is this type inference not working with this Lambda expression scenario?

查看:147
本文介绍了为什么此类型推断不适用于此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 使用原始类型:

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 of boolean
  • value in the lambda should be inferred to Value<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 (注意下面的带有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 <通过查看lambda的主体,/ code>是 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.

这篇关于为什么此类型推断不适用于此Lambda表达式方案?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆