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) { }
}
为什么?这个警告意味着什么?这些方法有何模棱两可?抑制警告是否安全?
答案 0 :(得分:19)
由于重载决策,目标类型和类型推断之间的有趣交集,会出现这些警告。编译器正在为您提前考虑并警告您,因为大多数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放在一起的方式。如果您真的想要使用这些参数类型的方法,您可以重命名方法testInt
,testLong
等,并避免完全重载。请注意,Java SE API在类似情况下执行了此操作,例如comparingInt
上的comparingLong
,comparingDouble
和Comparator
;以及mapToInt
上的mapToLong
,mapToDouble
和Stream
。