编译器不推断 System.out::println 功能接口

Compiler not inferring System.out::println functional interface

我有一个重载方法,它采用两个不同的功能接口作为参数(RunnbleSupplier)。 System.out.println 显然只兼容 Runnable,因为它是 void 方法。然而编译器仍然声称调用是不明确的。这怎么可能?

import java.util.function.Supplier;

public class GenericLambdas {
    public static void main(String[] args) {
        wrap(System.out::println);   // Compiler error here
        wrap(() -> {});              // No error
        wrap(System.out::flush);     // No error
    }

    static <R> void wrap(Supplier<R> function) {}

    static void wrap(Runnable function) {}
}

编译器输出:

Error:Error:line (5)java: reference to wrap is ambiguous
    both method <R>wrap(java.util.function.Supplier<R>) in GenericLambdas and method wrap(java.lang.Runnable) in GenericLambdas match 
Error:Error:line (5)java: incompatible types: cannot infer type-variable(s) R
    (argument mismatch; bad return type in method reference
      void cannot be converted to R)

基于第二个错误 (argument mismatch, void cannot be converted to R),编译器不应该能够消除调用的歧义吗?这将解决两个编译器错误(因为它既不会模棱两可,也不会尝试将 void 转换为 R)。

为什么() -> {}System.out::flush能够解析?它们具有与 System.out.println 相同的签名。假设 System.out.println 被带有参数的版本重载,但是这些重载版本中的 none 匹配 SupplierRunnable,所以我不知道它们会怎样此处相关。

编辑:

似乎 编译并且 运行 与 Eclipse compiler。哪个编译器是正确的,或者是否允许任何一种行为?

这似乎是 javac 编译器中的错误。问题出在重载的 println() 方法上。它根据您编写的类型有不同的实现:intlongString 等。因此表达式:System.out::println 有 10 种方法可供选择。其中一个可以推导为Runnable,另外9个推导为Consumer<T>.

不知何故,javac 编译器无法从此表达式推断出正确的方法实现。 wrap(() -> {}) 编译正确,因为这个表达式只有一种可能的解释 – Runnable.

我不确定在 JLS 规则下是否允许使用此类表达式。但以下代码使用 javac 正确编译(并且运行时没有运行时问题):

wrap((Runnable)System.out::println);

这个转换似乎为编译器提供了正确推断类型所需的信息,这有点奇怪。我不知道强制转换表达式可以用于类型推断。

寻找合适的 println 版本会忽略 return 类型。参见 JLS 15.13.2。包含它是没有意义的,因为不能有具有相同参数但 return 类型不同的方法的两个版本。但是现在编译器遇到了一个问题:Supplier#getRunnable#run 需要相同的参数 (none)。所以有一个 println 可以匹配两者。请记住,在此阶段,编译器仅尝试查找方法。编译器基本上遇到了与这段代码相同的问题:

public static void main(String[] args) {
 test(System.out::println);
}

public static void test(Runnable r) {}
public static void test(Consumer<String> r) {}

println() 匹配 Runnable#runprintln(String) 匹配 Consumer#accept。我们没有提供目标类型,所以情况不明确。

选择方法后,可以正确推断目标类型,在这个阶段return类型是相关的。参见 JLS 15.13.2。所以这段代码当然会失败:

public static void main(String[] args) {
    wrap(System.out::println);
}

static <R> void wrap(Supplier<R> function) {}

编译器检测到歧义时会立即抛出错误。尽管在这个 SO 问题背后提出的 bug report has been raised and accepted for this behavior, the comments there indicate that Oracle's JDK may be adhering to the JLS more faithfully than ECJ (despite its nicer behavior). A later bug report 被解决为 "Not an issue",表明经过内部辩论,Oracle 已决定 javac 的行为是正确的。