为什么这个 Java 方法调用被认为是模棱两可的?

我遇到了一个奇怪的错误消息,我认为它可能不正确。请考虑以下代码:

public class Overloaded {
    public interface Supplier {
        int get();
    }

    public interface Processor {
        String process(String s);
    }

    public static void load(Supplier s) {}
    public static void load(Processor p) {}

    public static int genuinelyAmbiguous() { return 4; }
    public static String genuinelyAmbiguous(String s) { return "string"; }

    public static int notAmbiguous() { return 4; }
    public static String notAmbiguous(int x, int y) { return "string"; }

    public static int strangelyAmbiguous() { return 4; }
    public static String strangelyAmbiguous(int x) { return "string"; }
}

如果我有一个看起来像这样的方法:

// Exhibit A
public static void exhibitA() {
    // Genuinely ambiguous: either choice is correct
    load(Overloaded::genuinelyAmbiguous); // <-- ERROR
    Supplier s1 = Overloaded::genuinelyAmbiguous;
    Processor p1 = Overloaded::genuinelyAmbiguous; 
}

我们得到的错误是完全有道理的;可以将参数分配给任一参数,因此我们得到一个错误,指出方法调用不明确。load()

相反,如果我有一个看起来像这样的方法:

// Exhibit B
public static void exhibitB() {
    // Correctly infers the right overloaded method
    load(Overloaded::notAmbiguous);
    Supplier s2 = Overloaded::notAmbiguous;
    Processor p2 = Overloaded::notAmbiguous; // <-- ERROR
}

调用很好,并且正如预期的那样,我无法将方法引用分配给两者,并且因为它没有歧义:不能分配给。load()SupplierProcessorOverloaded::notAmbiguousp2

现在是奇怪的那个。如果我有这样的方法:

// Exhibit C
public static void exhibitC() {
    // Complains that the reference is ambiguous
    load(Overloaded::strangelyAmbiguous); // <-- ERROR
    Supplier s3 = Overloaded::strangelyAmbiguous;
    Processor p3 = Overloaded::strangelyAmbiguous; // <-- ERROR
}

编译器抱怨对 的调用是不明确的 (),但与附件 A 不同,我无法将方法引用同时分配给 和 。如果它真的是模棱两可的,我觉得我应该能够像在图表A中一样分配和两个重载参数类型,但是我在声明时得到了一个错误。图C中的第二个错误是有道理的,不能分配给,但如果它是不可分配的,为什么它仍然被认为是模棱两可的?load()error: reference to load is ambiguousSupplierProcessors3p3p3error: incompatible types: invalid method referenceOverloaded::strangelyAmbiguousProcessor

方法引用推理似乎只在确定要选择哪个重载版本时查看功能接口的 arity。在变量赋值中,会检查参数的 arity 类型,这会导致重载方法和变量赋值之间存在这种差异。

在我看来,这就像一个错误。如果不是,至少错误消息是不正确的,因为当两个选择之间只有一个是正确的时,可以说没有歧义。


答案 1

你的问题与此问题非常相似。

简短的回答是:

Overloaded::genuinelyAmbiguous;
Overloaded::notAmbiguous;
Overloaded::strangelyAmbiguous;

所有这些方法引用都是不精确的(它们有多个重载)。因此,根据 JLS §15.12.2.2.2.,在重载解析期间,它们从适用性检查中跳过,这会导致歧义。

在这种情况下,您需要显式指定类型,例如:

load((Processor) Overloaded::genuinelyAmbiguous);
load(( Supplier) Overloaded::strangelyAmbiguous);

答案 2

方法引用和重载,只是...不要。从理论上讲,你是正确的 - 对于编译器来说,这应该很容易推断出来,但我们不要混淆人类和编译器。

编译器看到对的调用并说:“嘿,我需要调用该方法。很酷,我可以吗?嗯,有2个。当然,让我们匹配论点”。好吧,该参数是对重载方法的方法引用。因此,编译器在这里变得非常混乱,它基本上是说:“如果我能分辨出你指向哪个方法引用,我可以调用,但是,如果我能说出你想调用哪个方法,我可以推断出正确的”,因此它只是兜圈子,追逐它的故事。在编译器的“头脑”中做出这个决定是我能想到的最简单的解释方式。这带来了一个金色的坏做法 - 方法重载和方法引用是一个坏主意loadloadloadstrangelyAmbiguous

但是,你可能会说 - ARITY!参数的数量是编译器在决定这是否是重载时(可能)做的第一件事,正是你的观点:

Processor p = Overloaded::strangelyAmbiguous;

对于这个简单的例子,编译器确实可以推断出正确的方法,我的意思是我们,人类可以,对于编译器来说应该是一个不费吹灰之力的人。这里的问题是,这是一个只有2种方法的简单案例,那么100 * 100个选择呢?设计师必须允许一些东西(让我们说高达5 * 5并允许这样的分辨率)或完全禁止 - 我想你知道他们采取的路径。很明显,如果你使用lambda,为什么这会起作用 - arity在那里,显式的。

关于错误消息,这并不是什么新鲜事,如果你对lambda和方法引用玩得足够多,你会开始讨厌错误消息:“不能从静态上下文中引用非静态方法”,而实际上与此无关。IIRC这些错误消息已经从java-8及更高版本进行了改进,您永远不知道此错误消息是否也会在java-15中得到改善,比方说。