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

Joh*_*ica 14 java overloading compiler-warnings java-8 functional-interface

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) { }
}
Run Code Online (Sandbox Code Playgroud)

当我编译这个时,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
Run Code Online (Sandbox Code Playgroud)

如果我改变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) { }
}
Run Code Online (Sandbox Code Playgroud)

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

Stu*_*rks 20

出现这些警告是因为重载决策,目标类型和类型推断之间的有趣交集.编译器正在为您提前考虑并警告您,因为大多数lambda都是在没有显式声明的类型的情况下编写的.例如,考虑这个电话:

    test(1, i -> { });
Run Code Online (Sandbox Code Playgroud)

是什么类型的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
Run Code Online (Sandbox Code Playgroud)

(有趣的是,它提到了floatdouble重载,但是如果你对其中一个进行评论,则会出现与long重载相同的错误.)

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

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

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

    test(1, (Integer i) -> { });
Run Code Online (Sandbox Code Playgroud)

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

    test(1, (Consumer<Integer>)i -> { });
Run Code Online (Sandbox Code Playgroud)

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

这种Supplier情况不会发生这些警告,因为供应商的类型可以通过本地推理确定,无需任何类型推断.

您可能想要重新考虑将这个API放在一起的方式.如果你真的想用这些参数类型的方法,那你最好重新命名的方法testInt,testLong等,完全避免超载.需要注意的是,Java SE的API已经在类似的情况下做到了这一点,比如comparingInt,comparingLongcomparingDoubleComparator; 而且mapToInt,mapToLongmapToDoubleStream.