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?

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
  • What happens when you try to call the warned-about functions? – Jeffrey Bosboom Mar 19 '15 at 02:46
  • 1
    It appears that the warning would only be issued when: a. it's a functional interface (i.e. Consumer, Supplier), AND b. any of the interface's methods contains any parameters... As a test I made my own copies of the Consumer/Supplier interfaces and twiddled with them. Sadly I don't know enough about Java functional to know why a warning would be produced for this. – Kai Mar 19 '15 at 02:57
  • Correction: point A is incorrect, it doesn't needs to be a functional interface, just any interface (class produces no warning) with only one (non-default) method is defined. Also, I've changed the parameters of test(int, Consumer) to something next to impossible to be ambiguous like test(Object, Consumer) v.s. test(Map, Consumer) and yet the warning is still thrown. As such, unless you employ some really wacky programming, I really don't think you'll need to worry about this. – Kai Mar 19 '15 at 03:15
  • @Kai: An `interface` with exact one abstract method *is* a functional interface. There is no other criteria. – Holger Mar 19 '15 at 12:43
  • @Holger yeah haven't looked into lambda until today, I find Oracle's articles on the subject quite well written: [Java 8: Lambdas, Part 1](http://www.google.com.tw/url?sa=t&rct=j&q=Java+8%3A+Lambdas%2C+Part+1&source=web&cd=1&cad=rja&uact=8&ved=0CB4QFjAA&url=http%3A%2F%2Fwww.oracle.com%2Ftechnetwork%2Farticles%2Fjava%2Farchitect-lambdas-part1-2080972.html&ei=iNQKVdf-B4O48gWqkILACQ&usg=AFQjCNH1GAXK0Qm9zIULHZCvDpqg0c_3Fg&sig2=zAczpndst6Mr_H8oA-hyHg), [Java 8: Lambdas, Part 2](http://www.oracle.com/technetwork/articles/java/architect-lambdas-part2-2081439.html) – Kai Mar 19 '15 at 13:54

1 Answers1

22

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.

Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
  • 1
    We (Eclipse JDT) would like to synchronize `ecj` with `javac` in this regard to fulfill JLS 9.6.4.5. Could you please provide / link a description when exactly javac isses warnings that are suppressable by `@SuppressWarnings("overload")`? – Stephan Herrmann Jan 12 '20 at 22:31
  • 1
    I'm still hoping for some definition / specification of the analysis that raises warnings suppressable by `@SuppressWarnings("overload")`, because lacking that, ecj can only guess similar rules. Most likely we'd get lots of new warnings where ecj deems the suppression unnecessary which is needed with javac or vice versa. As per JLS 9.6.4.5 the javac team should really be more transparent about such issues! – Stephan Herrmann Jan 02 '22 at 11:56
  • @StephanHerrmann I do not have an answer for you, I just want to clarify that the magical incantation that appears to work is not `@SuppressWarnings("overload")`, it is `@SuppressWarnings("overloads")`. – Mike Nakis Feb 03 '22 at 00:34
  • And note that "appears to work" is relative: even though the warning is suppressed at the place where the overload is declared, the overload is practically unusable from that moment on, because the ambiguity causes a compilation error at each call site. To clear the ambiguity at each call site you need so much extra notation that it ends up being much easier to just use different methods instead of overloads of the same method. So, chances are that even if you somehow manage to provide support for this, it won't be of any practical use to anybody. – Mike Nakis Feb 03 '22 at 00:40
  • @MikeNakis I totally agree that overloading should rather be avoided than seeking means to use it in more and more sophisticated manners. Where suitable I keep spreading the advice to completely avoid any combination of overloading and functional interfaces. Just as a tool implementor I can hardly sit back and claim: "you shouldn't be doing this". Raising a warning is helpful. For many warnings it is also helpful to give control back to the developer to silence a particular warning. Whether or not such silencing is prudent in this case is indeed a good question. – Stephan Herrmann Feb 03 '22 at 13:29