Java 8 Consumer/Function Lambda 歧义

Java 8 Consumer/Function Lambda Ambiguity

我有一个重载方法,它分别接受一个 Consumer 对象和一个 Function 对象,并且 return 是一个匹配相应 Consumer/Function 的泛型类型。我认为这会很好,但是当我尝试使用 lambda 表达式调用任一方法时,我收到一条错误消息,指示对该方法的引用不明确。

根据我对 JLS §15.12.2.1. Identify Potentially Applicable Methods: 的阅读,编译器似乎应该知道我的带有 void 块的 lambda 与 Consumer 方法匹配,而带有 return 类型的 lambda 与 Function 方法匹配。

我整理了以下无法编译的示例代码:

import java.util.function.Consumer;
import java.util.function.Function;

public class AmbiguityBug {
  public static void main(String[] args) {
    doStuff(getPattern(x -> System.out.println(x)));
    doStuff(getPattern(x -> String.valueOf(x)));
  }

  static Pattern<String, String> getPattern(Function<String, String> function) {
    return new Pattern<>(function);
  }

  static ConsumablePattern<String> getPattern(Consumer<String> consumer) {
    return new ConsumablePattern<>(consumer);
  }

  static void doStuff(Pattern<String, String> pattern) {
    String result = pattern.apply("Hello World");
    System.out.println(result);
  }

  static void doStuff(ConsumablePattern<String> consumablePattern) {
    consumablePattern.consume("Hello World");
  }

  public static class Pattern<T, R> {
    private final Function<T, R> function;

    public Pattern(Function<T, R> function) {
      this.function = function;
    }

    public R apply(T value) {
      return function.apply(value);
    }
  }

  public static class ConsumablePattern<T> {
    private final Consumer<T> consumer;

    public ConsumablePattern(Consumer<T> consumer) {
      this.consumer = consumer;
    }

    public void consume(T value) {
      consumer.accept(value);
    }
  }
}

我还发现了一个 similar Whosebug post 结果是一个编译器错误。我的情况非常相似,虽然有点复杂。在我看来,这仍然像是一个错误,但我想确保我没有误解 lambda 的语言规范。我正在使用 Java 8u45,它应该有所有最新的修复。

如果我将我的方法调用更改为包装在一个块中,一切似乎都可以编译,但这会增加额外的冗长,许多自动格式化程序会将其重新格式化为多行。

doStuff(getPattern(x -> { System.out.println(x); }));
doStuff(getPattern(x -> { return String.valueOf(x); }));

这一行肯定有歧义:

doStuff(getPattern(x -> String.valueOf(x)));

从链接的 JLS 章节重新阅读此内容:

A lambda expression (§15.27) is potentially compatible with a functional interface type (§9.8) if all of the following are true:

  • The arity of the target type's function type is the same as the arity of the lambda expression.

  • If the target type's function type has a void return, then the lambda body is either a statement expression (§14.8) or a void-compatible block (§15.27.2).

  • If the target type's function type has a (non-void) return type, then the lambda body is either an expression or a value-compatible block (§15.27.2).

Consumer 的情况下,您有一个 statement expression,因为任何方法调用都可以用作语句表达式,即使该方法是非空的。例如,您可以简单地这样写:

public void test(Object x) {
    String.valueOf(x);
}

没有任何意义,但编译完美。您的方法可能有副作用,编译器不知道。例如,如果它 List.add 总是 returns true 并且没有人关心它的 return 值。

当然,这个 lambda 也符合 Function 的条件,因为它是一个表达式。因此它是模棱两可的。如果你有一个表达式,而不是 语句表达式 ,那么调用将毫无问题地映射到 Function

doStuff(getPattern(x -> x == null ? "" : String.valueOf(x)));

当您将其更改为 { return String.valueOf(x); } 时,您创建了一个 value-compatible block,因此它与 Function 相匹配,但它不符合 void-compatible块。但是,您也可能遇到块问题:

doStuff(getPattern(x -> {throw new UnsupportedOperationException();}));

此块符合值兼容和空兼容的条件,因此您又遇到了歧义。另一个歧义块示例是无限循环:

doStuff(getPattern(x -> {while(true) System.out.println(x);}));

至于 System.out.println(x) 的情况有点棘手。它肯定符合 语句表达式 ,因此可以匹配到 Consumer,但似乎它匹配表达式以及规范说 method invocation is an expression. However it's an expression of limited use like 15.12.3 说:

If the compile-time declaration is void, then the method invocation must be a top level expression (that is, the Expression in an expression statement or in the ForInit or ForUpdate part of a for statement), or a compile-time error occurs. Such a method invocation produces no value and so must be used only in a situation where a value is not needed.

所以编译器完全符合规范。首先,它确定您的 lambda 主体既符合表达式(即使其 return 类型为 void:15.12.2.1 在这种情况下也不例外)和语句表达式,因此它也被认为是歧义。

因此对我来说,这两个语句都根据规范编译。 ECJ 编译器在此代码上产生相同的错误消息。

一般来说,当您的重载具有相同数量的参数并且仅在可接受的功能接口方面有所不同时,我建议您避免重载您的方法。即使这些功能接口具有不同的元数(例如,ConsumerBiConsumer):您在使用 lambda 时不会有问题,但在方法引用方面可能会有问题。在这种情况下,您的方法只是 select 不同的名称(例如,processStuffconsumeStuff)。