当前位置: 首页 > 面试题库 >

为什么此类型推断不适用于此Lambda表达式方案?

鄂琛
2023-03-14
问题内容

我有一个奇怪的场景,在使用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>

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>)

阶段:方法适用性阶段
实际情况:在
类型参数中传递的实际参数:显式类型参数
候选项:可能适用的方法


实际情况是<none>因为我们的隐式lambda
与适用性无关。

编译器解决您的调用foo来命名的唯一方法fooFoo。它已经部分实例化到Foo.<Object> foo(因为没有实际值或type-
args),但是可以在推论阶段改变。

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:在其中进行调用的上下文。如果方法调用是分配的一部分,则它将在左侧。如果方法调用本身是方法调用的一部分,则它将是参数类型。

由于您的方法调用是悬空的,因此没有目标类型。由于没有目标类型,因此无法进行进一步的推断,fooT推断为Object

分析

编译器在推理期间不使用隐式类型的lambda。在某种程度上,这是有道理的。通常,给定的类型param -> BODY是您将无法编译。如果您确实尝试推断from
的类型,则可能会导致鸡和蛋的类型出现问题。在将来的Java版本中,可能对此进行一些改进。BODY``param``param``BODY

解决方案

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下文)。这使deferred-
instantiation可以推断应该使用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中指定的内容。我可以四处挖掘,看看是否可以找到指定此行为的确切部分,但是类型推断符号使我头疼。

这也不能完全解释为什么更改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格式将允许延迟的实例推断TBoolean。如果我不得不推测的话,我可能会猜想,当编译器尝试将lambda适配到时Bar<T>,它可以通过查看lambda的主体来推断出这T一点Boolean。这意味着我先前的分析是不正确的。编译器
可以 对lambda主体执行类型推断,但只能对 出现在返回类型中的类型变量执行。



 类似资料:
  • 我有一个奇怪的场景,当使用lambda表达式时,类型推断没有像我预期的那样工作。以下是我真实场景的近似值: 我在倒数第二行得到的编译错误是 方法booleanValue()未为类型Object定义 如果我将lambda转换为

  • 问题内容: 我正在使用Java 的本地实现,该实现具有如下方法: 这两种方法可以编译并正常工作: 此方法无法编译: 错误: (我已经修剪了包限定符,以使错误更易读) 我可以通过指定类型进行编译: 但是为什么我需要呢?以及如何避免此代码混乱? 问题答案: 此代码应该起作用。 它在最新的JDK 1.8.0_121上编译。 无法在JDK 1.8.0-51上编译。 这意味着它在此版本的JDK中很可能是一个

  • 问题内容: 说,我有一种方法: 然后在尝试编译此代码时: 我得到一个错误。谁能解释为什么类型系统不能推断Collections.emptyList()应该是类型吗? 上面的示例显然是很人为的,但是我一直都在偶然地遇到这种限制,这确实很烦人。阅读了 Effective Java 之后,我发现您可以轻松地完成工作(必须说,当时对我来说是一个启示),并且一切都可以顺利编译,但是当您使用复杂的类型时,确实

  • 说吧,我有一个方法: 然后在尝试编译此代码时: 我收到错误

  • 问题内容: 我正在做一个小的javascript方法,该方法会接收到一个点列表,并且我必须阅读这些点才能在Google地图中创建多边形。 我在表格上收到这些要点: (lat,long),(lat,long),(lat,long) 因此,我完成了以下正则表达式: 我已经使用RegexPal和收到的确切数据进行了测试: 并且它可以正常工作,所以为什么当我在JavaScript中添加此代码后,结果中会收

  • 我没有正确使用注释吗? 编译错误: 注释工作正常,可以毫无问题地访问该网页。