问题 警告:[重载]方法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

如果我改变 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) { }
}

这是为什么?这个警告意味着什么?这些方法有何模棱两可?抑制警告是否安全?


3227
2018-03-19 02:09


起源

当你试图调用警告函数时会发生什么? - Jeffrey Bosboom
似乎只有在下列情况下才会发出警告:a。它是一个功能界面(即消费者,供应商),和b。任何接口的方法都包含任何参数......作为测试,我制作了自己的Consumer / Supplier接口副本,并对它们进行了改动。遗憾的是,我对Java的功能知之甚少,不知道为什么要为此产生警告。 - Kai
更正:A点不正确,它不需要是一个功能接口,只需要任何接口(类不产生警告),只定义一个(非默认)方法。另外,我已经将test(int,Consumer)的参数更改为接下来不可能像test(Object,Consumer)v.s.测试(地图,消费者)但仍然会发出警告。因此,除非你采用一些非常古怪的编程,否则我真的不认为你需要担心这一点。 - Kai
@Kai:An interface 用一个抽象的方法 是 功能界面。没有其他标准。 - Holger
@Holger是的,直到今天还没有看过lambda,我发现Oracle关于这个主题的文章写得非常好: Java 8:Lambdas,第1部分, Java 8:Lambdas,第2部分 - Kai


答案:


出现这些警告是因为重载决策,目标类型和类型推断之间存在有趣的交集。编译器正在为您提前考虑并警告您,因为大多数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

(有趣的是,它提到了 float 和 double 重载,但如果你评论其中一个,你会得到相同的错误 long 超载。)

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

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

为了消除呼叫的歧义,人们必须写作

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

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

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

但这可能会更糟。您可能不希望API的调用者必须在每个调用点都与这种事情搏斗。

这些警告不会发生 Supplier 因为供应商的类型可以通过本地推理确定,没有任何类型推断。

您可能想要重新考虑将这个API放在一起的方式。如果您确实需要具有这些参数类型的方法,则可以重命名方法 testInttestLong等,并避免完全超载。请注意,Java SE API已在类似情况下执行此操作,例如 comparingIntcomparingLong,和 comparingDouble 上 Comparator;并且 mapToIntmapToLong,和 mapToDouble 上 Stream


16
2018-03-19 05:18



非常好的解释!阅读后没有留下任何问题。 - TWiStErRob