8

我有一个重载方法,它采用两个不同的功能接口作为参数(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被带有参数的版本重载,但这些重载版本都不匹配Supplieror Runnable,所以我看不出它们在这里有什么关系。

编辑:

似乎这确实可以使用Eclipse 编译器进行编译和运行。哪个编译器是正确的,或者任何一种行为都允许?

4

2 回答 2

4

查找适当版本的println忽略返回类型。见JLS 15.13.2。包含它是没有意义的,因为不能有两个版本的方法具有相同的参数但返回类型不同。但是现在编译器遇到了一个问题:两者都Supplier#get期望Runnable#run相同的参数(无)。所以有一个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。我们没有提供目标类型,所以情况不明确。

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

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

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

编译器在检测到歧义时会立即抛出错误。尽管已针对此行为提出并接受了错误报告,但那里的评论表明 Oracle 的 JDK 可能比 ECJ 更忠实地遵守 JLS(尽管它的行为更好)。后来在这个 SO 问题的后面提出的错误报告被解决为“不是问题”,这表明,经过内部辩论,Oracle 已确定该javac行为是正确的。

于 2015-10-15T13:08:42.610 回答
2

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

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

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

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

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

于 2015-10-15T02:10:13.390 回答