当前位置: 首页 > 知识库问答 >
问题:

为什么这种类型推断不适用于这个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()未为类型Object定义

如果我将lambda转换为

Foo.foo((Bar<Boolean>)value -> true).booleanValue();

或者如果我将Bar.apply的方法签名更改为使用原始类型:

T apply(Value value);

然后问题就解决了。我希望这个工作的方式是:

  • Foo.foo调用应该推断boolean
  • 的返回类型 lambda中的
  • value应推断为Value

为什么这个推理不能按预期工作,我如何更改这个API使其按预期工作?


共有3个答案

乜胜泫
2023-03-14

解决这个问题的简单方法是对foo的方法调用进行类型声明:

Foo.<Boolean>foo(value -> true).booleanValue();

编辑:我找不到关于为什么这是必要的具体文档,就像其他人一样。我怀疑这可能是因为原始类型,但那是不对的。无论如何,此语法是使用目标类型调用的。Lambdas中也有目标类型。虽然我找不到原因,但我在任何地方都找不到关于为什么这个特定用例是必要的文档。

编辑2:我发现了这个相关的问题:

泛型类型推断不适用于方法链接?

看起来这是因为你在这里链接方法。根据接受答案中引用的JSR注释,这是故意省略功能,因为编译器无法在两个方向的链式方法调用之间传递推断的泛型类型信息。因此,当它到达对布尔值的调用时,整个类型的数据被擦除。在中添加目标类型通过手动提供约束而不是让编译器使用JLS§18中概述的规则进行决策来消除这种行为,JLS§18似乎根本没有提到这一点。这是我能想到的唯一信息。如果有人找到更好的,我很想看看。

史宸
2023-03-14

对lambda参数类型的推断不能依赖于lambda体。

编译器面临着一项艰巨的工作,试图理解隐式lambda表达式

    foo( value -> GIBBERISH )

在编译乱码之前,必须首先推断值的类型,因为乱码的解释通常取决于值的定义。

(在您的特例中,GIBBERISH恰好是一个独立于值的简单常数)

Javac必须推断值

对函数接口进行更改后,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 ?

T被推断为字符串;lambda的身体没有参与推断。

在这个例子中,javac的行为对我们来说似乎非常合理;它可能防止了编程错误。您不希望推理太强大;如果我们编写的所有内容都以某种方式编译,我们将失去编译器为我们发现错误的信心。

还有其他示例,其中lambda体似乎提供了明确的约束,但编译器无法使用该信息。在Java中,必须先固定lambda参数类型,然后才能查看主体。这是一个深思熟虑的决定。相比之下,C愿意尝试不同的参数类型,看看是什么使代码能够编译。Java认为这太危险了。

在任何情况下,当隐式lambda失败时(这种情况经常发生),为lambda参数提供显式类型;在您的情况下,(值

满才
2023-03-14

使用一些隐藏的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>)

阶段:方法适用性阶段
实际:在
type-args中传递的实际参数:显式类型参数
候选:潜在适用的方法

实际值<代码>

编译器将您对foo的调用解析为Foo中唯一名为foo的方法。它已部分实例化为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推断为对象。

编译器在推理期间不使用隐式类型的lambda。在某种程度上,这是有道理的。通常,给定-

Foo。

此解决方案为foo提供了一个显式类型参数(请注意下面带有type-args部分的)。这将方法签名的部分实例化更改为(条形图

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((值

该解决方案显式地键入lambda,从而使其与适用性相关(注意下面的实际值)。这将方法签名的部分实例化更改为(Bar

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((巴

同上,但味道略有不同。

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

布尔b=Foo。foo(值-

该解决方案为方法调用提供了一个明确的目标(请参见下面的目标类型)。这允许延迟实例化推断类型参数应该是布尔的,而不是对象的(参见下面的实例化签名)。

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中指定的。我可以四处挖掘,看看是否能找到指定此行为的确切部分,但是类型推断符号让我头疼。

这也不能完全解释为什么将条形图更改为使用原始值可以解决这个问题:

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时

 类似资料:
  • 问题内容: 我有一个奇怪的场景,在使用lambda表达式时,类型推断无法按预期工作。这是我实际情况的近似值: 我倒数第二行的编译错误是 未为对象类型定义方法booleanValue() 如果我将lambda转换为: 或者如果我将方法签名更改为使用原始类型: 然后问题就解决了。我希望它能起作用的方式是: 调用应推断返回类型为 在lambda中应该推断为。 为什么这种推论不能按预期方式工作?如何更改此

  • 我一直在尝试编写一个程序来实现任意域上的多项式,一种数学结构。我选择了Haskell作为编程语言,我使用了语言扩展。但是,我不明白为什么GHCi不能推导出的约束条件。 在我看来,保证是的实例,这意味着是的实例。所以调用就像调用一样,应该是合理的。此外,我已经编写了作为约束,并且的构造函数具有的形状,因此它还应该知道的类型是的实例。 显然,译员的想法不同。我哪里搞错了?

  • 我使用的是Java中的<code>或者</code>的本地实现,它有如下方法: 这两种方法可以编译并正常工作: 此方法不编译: 错误: (我去掉了包限定符,使错误更加易读) 我可以通过指定类型来编译它: 但我为什么需要这样做?我如何避免这种代码混乱?

  • 问题内容: 试图了解正则表达式,我在重复的部分:。 我有以下代码: 如您所见,两个字符串都不匹配该模式。为什么会这样呢? 问题答案: 您不应该在逗号后加空格,这是多余的。

  • 我正在尝试编译我正在编写的Java Web应用程序,并且我遇到了编译错误,我不知道该怎么办。从我完成的谷歌搜索中,我发现了这个SO问题,但是提问者使用的是EJB,而我的错误是在JPA实体类中。 下面是maven构建错误。 这是我的用户类文件。 我查看了javax.persistence.Index JavaDoc,我的声明是正确的,所以我在这里被难住了,有人知道我做错了什么吗?感谢您的时间和考虑。

  • 当我跑的时候 我在Java中得到错误。为什么?这相当于false==false,这是真的。这不是Java特有的,我在其他语言中也得到了相同的结果。这是因为短路评估吗?似乎左右双方仍会/应该进行比较。