警告:[重载] 方法 m1 与方法 m2 可能不明确
Warning: [overloads] method m1 is potentially ambiguous with method 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) { }
}
这是为什么?这个警告是什么意思?这些方法是如何模棱两可的?抑制警告是否安全?
这些警告的出现是因为重载解析、目标类型和类型推断之间有趣的交集。编译器会提前为您考虑并警告您,因为大多数 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
情况下不会出现这些警告,因为 Supplier 的类型可以通过本地推理确定,无需任何类型推断。
您可能需要重新考虑将 API 组合在一起的方式。如果您确实需要具有这些参数类型的方法,您最好将方法重命名为 testInt
、testLong
等,并避免完全重载。请注意,Java SE API 已经在类似情况下这样做了,例如 comparingInt
、comparingLong
和 comparingDouble
在 Comparator
上; mapToInt
、mapToLong
和 mapToDouble
Stream
.
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) { }
}
这是为什么?这个警告是什么意思?这些方法是如何模棱两可的?抑制警告是否安全?
这些警告的出现是因为重载解析、目标类型和类型推断之间有趣的交集。编译器会提前为您考虑并警告您,因为大多数 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
情况下不会出现这些警告,因为 Supplier 的类型可以通过本地推理确定,无需任何类型推断。
您可能需要重新考虑将 API 组合在一起的方式。如果您确实需要具有这些参数类型的方法,您最好将方法重命名为 testInt
、testLong
等,并避免完全重载。请注意,Java SE API 已经在类似情况下这样做了,例如 comparingInt
、comparingLong
和 comparingDouble
在 Comparator
上; mapToInt
、mapToLong
和 mapToDouble
Stream
.