2

I have following classes

interface 1

 package test;

    public interface TODO {

        boolean test();
    }

interface 2

package test;

@FunctionalInterface
public interface FuncN {
  State zip(State ...states);
}

class 1

package test;

public class Test {

    public static Test define(FuncN zipperFunc,TODO... tasks) {
        return null;
    }


    public static Test define(TODO... tasks) {
        return null;
    }
}

class 2

package test;

public class State {
    public static State mergeStates(State ...states) {
        return null;
    }
}

main class

package test;

public class Main {
    public static void main(String[] args) {
      Test.define(State::mergeStates,()->true);
    }
}

The class main doesn't compile, throws error

reference to define is ambiguous Test.define(State::mergeStates,()->true); ^ both method define(FuncN,TODO...) in Test and method define(TODO...) in Test match

Class below does compile:

package test;

public class Main {
    public static void main(String[] args) {
      Test.define(states->State.mergeStates(states),()->true);
    }
}

However i don't see any ambiguity. The signatures of FuncN and TODO are completely different, i don't think compiler should mistake them for one another.

Correct me if i am wrong.

P.S. Error is not reproducible using eclipse, so i would recommend creating a folder test creating all java files in that and run javac test/Main.java

Shariq
  • 249
  • 4
  • 15
  • I can't reproduce. `Test.define(State::mergeStates,()->true);` passes compilation for me. – Eran May 03 '18 at 07:30
  • my jvm specs are following: java version "1.8.0_171" Java(TM) SE Runtime Environment (build 1.8.0_171-b11) Java HotSpot(TM) 64-Bit Server VM (build 25.171-b11, mixed mode) javac 1.8.0_171. its not raising any error in eclipse, i would recommend creating a folder test putting all files in that and then running javac test/Main.java this is how error is thrown. – Shariq May 03 '18 at 07:38
  • Since `mergeState` is a *varargs* method, `State::mergeStates` is an *inexact method reference*, which needs the target type to get fully resolved, but the target type is not known before knowing which `define` method will be called, but this decision has to be made without considering the `State::mergeStates` argument, whose functional signature is not known. While it would be possible to rule out one variant, e.g. by just trying, the specification does not include such a test (deliberately, to not increase the complexity further). – Holger May 03 '18 at 07:54
  • So when you make the target method non-*varargs*, i.e. `public class State { public static State mergeStates(State[] states) { return null; } }`, the error will go away. – Holger May 03 '18 at 07:58
  • @Holger I can't tell why you are not answering this, it's yet again that case of *we need to resolve the method in order to find the target type, but we need to know the target type in order to resolve the method*. I swear that I have to go and re-read my notes about it everytime I encounter this – Eugene May 03 '18 at 08:10
  • @Eugene since Eclipse has been reported to compile this, I want to make some more research whether this corner case has another corner case… – Holger May 03 '18 at 08:11
  • @Holger eagerly waiting! – Eugene May 03 '18 at 08:12
  • @Holger it also works if i replace the method reference with lambda states->State.mergeStates(states) – Shariq May 03 '18 at 08:22
  • `states->State.mergeStates(states)` is a lambda expression with a well defined shape, where the *varargs* nature of the invoked method doesn’t matter. – Holger May 03 '18 at 08:26
  • @Holger no need for further research on why Eclipse accepts this: it's simply a matter of a missing check for pertinence to applicability in one code branch. Tracked via https://bugs.eclipse.org/534466 – Stephan Herrmann May 08 '18 at 14:27

1 Answers1

-1

It will compile if you cast the method reference call to FincN. there are two overload methods in Test class call 'define' so compiler confuse to select which one. so try to use as following.

    public static void main(String[] args){
      Test.define((FuncN) State::mergeStates,()->true);
    }
janith1024
  • 1,042
  • 3
  • 12
  • 25
  • Agreed, but my question was why the error is occurring in first place? Since signature of both interfaces is different the error should not occur. And how is eclipse able to it and javac is not. – Shariq May 03 '18 at 08:53
  • 1
    @Shariq most probably eclipse (`ECJ` compiler) has a bug against this, while `javac` is correct – Eugene May 03 '18 at 08:58
  • @Shariq I'm not using eclipse so I cannot comment on eclipse compiler. For the 'State::mergeStates' method reference can use for many functional interfaces such as Supplier,Function, Consumer, etc so you have to cast and tell which function you use. if there many selection is available – janith1024 May 03 '18 at 09:21
  • @janith1024 i am sorry but i don't think you quite understand how method references work. First of all method references or lambda are used in place of actual interface implementation so a lambda or method reference will try to match with the interface required. In this case the interface is FuncN. None of the Functional interfaces from JDK that you have mentioned will match as: Supplier doesn't have any input, Function takes fixed Argument as input and return fixed output, Consumer doesn't return anything. basically you are saying method references have to typecasted everytime. – Shariq May 03 '18 at 09:37
  • @janith1024 in my case two options available are FuncN and Todo but the signature of the method used as reference matches only FuncN – Shariq May 03 '18 at 09:40
  • @Shariq Supplier doesn't have any input yep it true and it also not related to this. I got those interfaces from JDK to express the idea but it went in wrong direction. thanks for that. – janith1024 May 03 '18 at 09:40
  • @Shariq 'reference matches only FuncN' but it don't know the compiler – janith1024 May 03 '18 at 09:42
  • @Shariq you would think that this overall is very easy, unless `public static void go(Predicate predicate){}; public static void go(Function function){}` and calling `go(i -> "test")` what do you think will happen? – Eugene May 03 '18 at 09:42
  • @Eugene well looking at them it seems go(i->"test") should match go(Function function){} and go(i->true) should match public static void go(Predicate predicate){}; but since you have asked i doubt that's the case. I agree sometimes things technically aren't as easy at they seem. – Shariq May 03 '18 at 09:46
  • @Shariq exactly - this will fail to compile... as said in the comment *we need to resolve the method in order to find the target type, but we need to know the target type in order to resolve the method*. question yourself another thing, why is there `map` or `mapToInt` and `mapToLong` but not a single overloaded `map`... – Eugene May 03 '18 at 09:48
  • 2
    @Shariq btw a very good read from the authors themselves here https://stackoverflow.com/a/21951311/1059372. I hope you understand by now, that this is an `ECJ` bug – Eugene May 03 '18 at 09:56
  • 1
    @Eugene now i understand what Holger Meant when he said "While it would be possible to rule out one variant, e.g. by just trying, the specification does not include such a test (deliberately, to not increase the complexity further)." Thanks for help i will read from the link you have provided. – Shariq May 03 '18 at 09:58