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

When I compile this with javac -Xlint Test.java I get a couple of warnings:

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

If I change Consumer to Supplier the warnings disappear. This program is warning free:

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

Why is that? What does this warning mean? How are these methods ambiguous? Is it safe to suppress the warning?

4

1 回答 1

21

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

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

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

为了消除通话的歧义,人们不得不写

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

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

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

但这可以说更糟。您可能不希望 API 的调用者在每次调用时都必须与这种事情作斗争。

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

您可能需要重新考虑将这个 API 组合在一起的方式。如果您真的想要具有这些参数类型的方法,您最好重命名方法testInttestLong等等,并完全避免重载。请注意,Java SE API 在类似的情况下已完成此操作,例如comparingIntcomparingLongcomparingDoubleon Comparator;还有mapToInt, mapToLong, 和mapToDoubleon Stream

于 2015-03-19T05:18:02.717 回答