8

Using Java 8, I get a compiler error for the following code:

public class Ambiguous {
    public static void call() {
        SomeDataClass data = new SomeDataClass();
        callee(data, SomeDataClass::getString);
        // compiler errors:
        // 1. at callee method name:
        // The method callee(SomeDataClass, Function<SomeDataClass,String>) is ambiguous for the type Ambiguous
        // 2. at lambda:
        // Type mismatch: cannot convert from boolean to String
        callee(data, d -> d.getRandom() > 0.5);
    }

    public static void callee(SomeDataClass data, Function<SomeDataClass, String> extractString) {
        System.out.println(extractString.apply(data));
    }

    public static void callee(SomeDataClass data, Predicate<SomeDataClass> check) {
        System.out.println(check.test(data));
    }
}

// token data class
final class SomeDataClass {
    public String getString() {
        return "string";
    }

    public final double getRandom() {
        return Math.random();
    }
}

So essentially the compiler says "I know you return boolean but you shouldn't, and if you don't I'm not sure what method to use" instead of "oh you're returning boolean, you must mean the Predicate version of the method"? How does this confusion get created?

I'd understand if Predicate<T> extends Function<T, Boolean> (so they have a common Type) but that's not the case.

I do know how to fix it; it's fine if I do

callee(data, (Predicate<SomeDataClass>) d -> d.getRandom() > 0.5);

but I'm curious what causes it.

daniu
  • 14,137
  • 4
  • 32
  • 53
  • 3
    FYI, this issue is addressed in JEP-302 and will be hopefully fixed in some of the future Java versions: http://openjdk.java.net/jeps/302 (see **Optional: Better disambiguation for functional expression**) – ZhekaKozlov May 04 '18 at 07:51
  • @ZhekaKozlov excellent! I could not remember the JEP number – Eugene May 04 '18 at 07:53

1 Answers1

1

This can be simplified a bit for clarity:

public static void m(Predicate<Integer> predicate){

}

public static void m(Function<Integer, String> function){

}

And calling it with:

m(i -> "test")

What do you think will happen? Same thing as in your question.

Compiler here has to know the method in order to find the target type, but it needs to know the target type in order to resolve the method (it's like a deadlock).

When you add a cast to Predicate..., you are creating an explicit target type, return type of which is taken into consideration for method overloading as far as I understand.

Eugene
  • 117,005
  • 15
  • 201
  • 306
  • 1
    But *I* can see that `i -> "test"` is not a `Predicate`, so why can't the compiler? Type erasure doesn't explain it. – slim May 04 '18 at 07:51
  • 1
    @slim *I can see* != *compiler can see*, this is a trivial example which could probably be made to work, but there are others that are not that easy I guess – Eugene May 04 '18 at 07:52
  • 1
    Sure, but a satisfactory answer is going to explain the limitations of what the compiler (or rather the language spec) can infer about the type, and why. Great job simplifying the question though. – slim May 04 '18 at 07:54
  • @slim that will take me a lot of time I guess (to find the exact parts in the JLS) :(, in the end it's about *implicit vs explicit lambda types* and lambda expression being poly expressions that depend on the context – Eugene May 04 '18 at 07:57
  • @slim well... it would have been strange if Holger would not had his fingers all over this... https://stackoverflow.com/a/42162812/1059372 I can't do better than that – Eugene May 04 '18 at 08:03
  • When you cast like `(Predicate)`, you are providing an explicit *target type*. You have an *explicit lambda type* when you specify `(SomeDataClass d) -> d.getRandom() > 0.5`, which makes the code work without the type cast, similar to `SomeDataClass::getString` which works as long as the method reference is *exact*, i.e. `getString` is not a *varargs* method and not overloaded. You could have added the same link you posted in a comment yesterday: https://stackoverflow.com/a/21951311/1059372 – Holger May 04 '18 at 08:54
  • @Holger thank you, I admit I get confused in this terminology :( – Eugene May 04 '18 at 08:58
  • About "what I think will happen" (or _though_, rather ;)): the same as what happens when doing `Function f = i -> "test";` or `Predicate p = i -> "test";`, respectively. Also, I'm especially confused about the "ambiguous" error - there are two methods, but their parameter types aren't ambiguous at all. – daniu May 04 '18 at 09:53
  • @daniu btw, did this answer your question? – Eugene May 10 '18 at 08:39
  • 1
    @eugene yes that was very enlightening, thank you. Forgot to set to answered, sorry. – daniu May 10 '18 at 09:25