java - 为什么这种类型推断不适用于这个 Lambda 表达式场景?

标签 java lambda java-8 type-inference

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

我在倒数第二行得到的编译错误是

The method booleanValue() is undefined for the type Object

如果我将 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>)

阶段:method applicability phase
actuals:传入的实际参数
type-args:显式类型参数
候选人:potentially applicable methods

实际值为 <none>因为我们的隐式类型 lambda 不是 pertinent to applicability .

编译器会解析您对 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>)

实例化签名:foo 的完全实例化签名.这是这一步的结果(此时不会再对 foo 的签名进行类型推断)。
目标类型:调用的上下文。如果方法调用是赋值的一部分,它将在左侧。如果方法调用本身是方法调用的一部分,则它将是参数类型。

由于您的方法调用是悬空的,因此没有目标类型。由于没有目标类型,因此无法对 foo 进行更多推断。和 T推断为Object .


分析

编译器在推理过程中不使用隐式类型的 lambda。在某种程度上,这是有道理的。一般来说,给定 param -> BODY ,您将无法编译BODY直到你有 param 的类型.如果您确实尝试推断 param 的类型来自 BODY ,它可能会导致鸡和蛋类型的问题。在 Java 的 future 版本中可能会对此进行一些改进。


解决方案

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)。这允许延迟实例化推断类型参数应该是 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 中指定的内容。我可以四处寻找,看看是否能找到指定此行为的确切部分,但是 type inference符号让我头疼。

这也不能完全解释为什么要更改 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> ,可以推断出TBoolean通过查看 lambda 的主体。这意味着我之前的分析是不正确的。编译器可以对 lambda 的主体执行类型推断,但仅限于出现在返回类型中的类型变量。

关于java - 为什么这种类型推断不适用于这个 Lambda 表达式场景?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31227149/

相关文章:

java - 在 JMockit 中多次重用期望 block

java - Apache BeanUtils.copyProperties 溢出了太多日志

Java 子类属性是否绑定(bind)?

java - 将字符串序列转换为 URI 的有效方法

java - 惯用地枚举 Java 8 中的对象流

java - 如何使用流将 List<DataObject> 转换为 Map<Integer, String>

Java8 Stream批处理以避免OutOfMemory

java - 使用递归解决二进制间隙

python - 如何使用 Python 检索 AWS Lambda 公共(public) IP 地址?

C++11 lambda 作为 std::function 参数传递 - 根据返回类型进行调度