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?
These warnings occur because of the fun intersection among overload resolution, target typing, and type inference. The compiler is thinking ahead a bit for you and is warning you because most lambdas are written without explicitly declared types. For example, consider this call:
test(1, i -> { });
What is the type of i
? The compiler can't infer it until it's completed overload resolution... but the value 1
matches all four of the overloads. Whichever overload is chosen would influence the target type of the second argument, which in turn would affect the type that's inferred for i
. There really isn't enough information here for the compiler to decide which method to call, so this line will actually result in a compile-time error:
error: reference to test is ambiguous
both method test(float,Consumer<Float>) in Test and
method test(double,Consumer<Double>) in Test match
(Interestingly, it mentions the float
and double
overloads, but if you comment one of these out, you get the same error with respect to the long
overload.)
One could imagine a policy where the compiler completed overload resolution using the most-specific rule, thereby choosing the overload with the int
arg. It would then have a definite target type to apply to the lambda. The compiler designers felt that this was too subtle, and that there would be cases where programmers would be surprised about which overload ended up being called. Instead of compiling programs in a possibly unexpected way, they felt it was safer to make this an error and force the programmer to disambiguate it.
The compiler is issuing warnings at the method declarations to indicate that the likely code a programmer would write to call one of these methods (as shown above) will result in a compile-time error.
To disambiguate the call, one would instead have to write
test(1, (Integer i) -> { });
or declare some other explicit type for the i
parameter. Another way is to add a cast before the lambda:
test(1, (Consumer<Integer>)i -> { });
but this is arguably worse. You probably don't want callers of your API to have to wrestle with this kind of thing at every point of call.
These warnings don't occur for the Supplier
case, because the type of a Supplier can be determined via local reasoning, without any type inference.
You'll probably want to rethink the way you're putting this API together. If you really want methods with those argument types, you might do well to rename the methods testInt
, testLong
, etc. and avoid overloading entirely. Note that the Java SE APIs have done this in similar cases, such as comparingInt
, comparingLong
, and comparingDouble
on Comparator
; and also mapToInt
, mapToLong
, and mapToDouble
on Stream
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With