警告:[重载] 方法 m1 与方法 m2 可能不明确

Warning: [overloads] method m1 is potentially ambiguous with method m2

import java.util.function.*;

class Test { 
    void test(int    foo, Consumer<Integer> bar) { }
    void test(long   foo, Consumer<Long>    bar) { }
    void test(float  foo, Consumer<Float>   bar) { }
    void test(double foo, Consumer<Double>  bar) { }
}

当我用 javac -Xlint Test.java 编译它时,我收到了一些警告:

Test.java:4: warning: [overloads] test(int,Consumer<Integer>) in Test is potentially ambiguous with test(long,Consumer<Long>) in Test
    void test(int    foo, Consumer<Integer> bar) { }
         ^
Test.java:6: warning: [overloads] test(float,Consumer<Float>) in Test is potentially ambiguous with test(double,Consumer<Double>) in Test
    void test(float  foo, Consumer<Float>   bar) { }
         ^
2 warnings

如果我将 Consumer 更改为 Supplier,警告就会消失。该程序没有警告:

import java.util.function.*;

class Test { 
    void test(int    foo, Supplier<Integer> bar) { }
    void test(long   foo, Supplier<Long>    bar) { }
    void test(float  foo, Supplier<Float>   bar) { }
    void test(double foo, Supplier<Double>  bar) { }
}

这是为什么?这个警告是什么意思?这些方法是如何模棱两可的?抑制警告是否安全?

这些警告的出现是因为重载解析、目标类型和类型推断之间有趣的交集。编译器会提前为您考虑并警告您,因为大多数 lambda 都是在没有明确声明类型的情况下编写的。例如,考虑这个调用:

    test(1, i -> { });

i 的类型是什么?编译器在完成重载解析之前无法推断它...但是值 1 匹配所有四个重载。无论选择哪个重载都会影响第二个参数的目标类型,这反过来会影响为 i 推断的类型。这里确实没有足够的信息让编译器决定调用哪个方法,所以这一行实际上会导致编译时错误:

    error: reference to test is ambiguous
           both method test(float,Consumer<Float>) in Test and
           method test(double,Consumer<Double>) in Test match

(有趣的是,它提到了 floatdouble 重载,但如果您注释掉其中之一,您会得到与 long 重载相同的错误。)

可以想象一种策略,其中编译器使用最具体的规则完成重载决策,从而选择带有 int arg 的重载。然后它将有一个明确的目标类型应用于 lambda。编译器设计者觉得这太微妙了,而且在某些情况下,程序员会对最终调用的重载感到惊讶。他们认为,与其以可能出乎意料的方式编译程序,不如将其作为错误并强制程序员消除歧义,这样更安全。

编译器在方法声明中发出警告,表明程序员编写的代码可能会调用这些方法之一(如上所示)将导致编译时错误。

为了消除调用的歧义,必须写成

    test(1, (Integer i) -> { });

或为 i 参数声明一些其他显式类型。另一种方法是在 lambda 之前添加一个转换:

    test(1, (Consumer<Integer>)i -> { });

但这可以说更糟。您可能不希望 API 的来电者在每次通话时都不得不为这种事情而苦恼。

Supplier 情况下不会出现这些警告,因为 Supplier 的类型可以通过本地推理确定,无需任何类型推断。

您可能需要重新考虑将 API 组合在一起的方式。如果您确实需要具有这些参数类型的方法,您最好将方法重命名为 testInttestLong 等,并避免完全重载。请注意,Java SE API 已经在类似情况下这样做了,例如 comparingIntcomparingLongcomparingDoubleComparator 上; mapToIntmapToLongmapToDouble Stream.