警告:[重载] 方法 m1 可能与方法 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

如果我更改为警告,则消失。此程序是无警告的:ConsumerSupplier

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) { }
}

为什么?此警告是什么意思?这些方法如何模棱两可?禁止显示警告是否安全?


答案 1

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

    test(1, i -> { });

什么是类型?编译器在完成重载解析之前无法推断它...但该值与所有四个重载匹配。无论选择哪种重载,都会影响第二个参数的目标类型,进而影响推断出的类型。这里确实没有足够的信息供编译器决定调用哪个方法,因此此行实际上会导致编译时错误:i1i

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

(有趣的是,它提到了 和 重载,但是如果您注释掉其中一个,则对于重载,您会得到相同的错误。floatdoublelong

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

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

为了消除呼叫的歧义,必须编写

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

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

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

但这可以说更糟。您可能不希望API的调用方在每个调用点都不得不与这种事情作斗争。

这些警告不会针对该案例发生,因为供应商的类型可以通过本地推理来确定,而无需任何类型推断。Supplier

您可能需要重新考虑将此 API 组合在一起的方式。如果您真的想要具有这些参数类型的方法,则最好重命名方法 ,等,并完全避免重载。请注意,Java SE API 在类似情况下也这样做了,例如 、 、 和 ;以及 、 和 上 。testInttestLongcomparingIntcomparingLongcomparingDoubleComparatormapToIntmapToLongmapToDoubleStream


答案 2