5

This issue may be similar to Reference is ambiguous with generics, but I guess it is a little different.

In this case

public class MyTest {

    public static void main(String[] args) {
        MyTest test = new MyTest();
        test.add(test::method);
    }

    public void add(Interface1 i1) {
    }

    public <T extends Interface2> void add(T i2) {
    }

    private void method() {
    }

    public interface Interface1 {
        void method1();
    }
    public interface Interface2 {
        boolean isMethod2();
        void setMethod2(boolean required);
    }

}

, it is compiled with Eclipse Neon.2, but not with javac 1.8.0_121 or 1.8.0_152, giving an error:

MyTest.java:5: error: reference to add is ambiguous

   test.add(test::method);
       ^

both method add(Interface1) in MyTest and method add(T) in MyTest match

where T is a type-variable:

T extends Interface2 declared in method add(T)



However, if

public <T extends Interface2> void add(T i2) {

is changed to:

public void add(Interface2 i2) {

then it id compiled by javac.


So, is it correct for javac to say T is a type-variable, and is not sub-type of Interface2, so it complains about ambiguity, and if so, then Eclipse is wrong?

Community
  • 1
  • 1
Ahmed Ashour
  • 5,179
  • 10
  • 35
  • 56
  • 2
    eclipse has its' own compiler, and the official Java compiler is the "correct one". So yes, it's correct to say that Eclipse is wrong. – Elliott Frisch Feb 24 '17 at 11:27
  • 2
    @ElliottFrisch I'm sorry, why exactly is `javac` the correct one? – biziclop Feb 24 '17 at 11:35
  • 3
    @biziclop Because the eclipse project has no (or minimal) involvement with the specification of Java (while Oracle **owns** Java). See also, [What is the difference between javac and the Eclipse compiler?](http://stackoverflow.com/questions/3061654/what-is-the-difference-between-javac-and-the-eclipse-compiler). From the linked questions' top answer, *One notable difference is that the Eclipse compiler lets you run code that didn't actually properly compile.* – Elliott Frisch Feb 24 '17 at 11:38
  • 3
    @ElliottFrisch So? I'm sorry, I still don't see your point. If `javac` is by definition the correct one, why do they keep fixing bugs in it? :) Surely whichever compiler conforms to the JLS is the correct one. Sometimes it's the Eclipse compiler, sometimes it's `javac`. In this case I suspect `javac` is the buggy one. (And the quote about the Eclipse compiler letting you run code that didn't compile is irrelevant here, that refers to a different thing.) – biziclop Feb 24 '17 at 11:41
  • I suspect `javac` complains about ambiguity because `T extends Interface2` might in theory resolve `T` to an `Interface3` that extends `Interface2`, defines default methods for `isMethod2()` and `setMethod2()`, and then declares an abstract `void method3()`. – biziclop Feb 24 '17 at 11:47
  • 3
    @biziclop `javac` isn't entirely buggy, `T` could be a Type that implements both `Interface1` and `Interface2`, and in that case both `add(T i2)` and `add(Interface1 i1)` are valid – niceman Feb 24 '17 at 11:47
  • @niceman Yes, that is definitely a potential source of ambiguity. But in this specific case we know it doesn't happen. – biziclop Feb 24 '17 at 11:49
  • ofcourse this doesn't mean the eclipse compiler is entirely wrong because there is not `T` that implements both interfaces in anyway here, what we have here is different strategies – niceman Feb 24 '17 at 11:49
  • @niceman, even if you add `Interface3` to extends both `Interface1` and `Interface2`, and change the method signature of `i2` to `public void add(Interface3 i2) {`, it will compile with `javac`. – Ahmed Ashour Feb 24 '17 at 12:47
  • @AhmedAshour You need to make `Interface3` a functional interface with a `void methodX()`, then it will be ambiguous. – biziclop Feb 24 '17 at 12:52

1 Answers1

0

The accepted answer to the stack exchange discussion you mentioned (https://stackoverflow.com/a/5365419/16595881) solves your issue.

To comment on the comments on this question, Oracle did not define a Java SE compiler as the 'correct' compiler for Java SE, but rather Oracle defined the specifications that a Java SE compiler must adhere to. In order for a Java SE compiler to be 'correct' (i.e. in order for something to be a Java SE compiler), it must implement those specifications. Just because Oracle defined both the specifications and a (questionable) implementation of those specifications, it is not guaranteed that the implementation is 'correct', although it likely is. In this case, your javac compiler correctly implements the Java SE language specifications, but Eclipse's compiler does not (it might now, being 5 years later).

Also, javac is a command-line tool on your PATH that points to an executable binary that can be arbitrary. This means instead of being a compiler, the binary executed by calling javac might simply print "I am not a Java SE compiler" or instead might produce nasal demons. The point is that the javac binary is not necessarily Oracle's implementation of the Java SE compiler specifications. For example, it could be an implementation by Sun (which was bought by Oracle in 2009) or by OpenJDK, to name a few. Anyway...

In the second definition of MyTest#add, T extends Interface2, but that does not limit T from also extending Interface1, because classes can implement multiple interfaces. So, it is possible to construct a class that implements both interfaces then pass one of its instances as the argument into MyTest#add, in which case both definitions of the method would be valid candidates, with neither being more appropriate then the other. Consider the following code example, where we replace the interfaces with abstract classes:

public class MyTest {

    public static void main(String[] args) {
        MyTest test = new MyTest();
        test.add(test.new Abstract1() {
            @Override
            void method1() {
                test.method();
            }
        });
    }

    public void add(Abstract1 a1) {
    }

    public <T extends Abstract2> void add(T a2) {
    }

    private void method() {
    }

    public abstract class Abstract1 {
        abstract void method1();
    }
    public abstract class Abstract2 {
        abstract boolean isMethod2();
        abstract void setMethod2(boolean required);
    }

}

The only difference here is the use of abstract classes instead of interfaces. If you were to call MyTest#add, there would be no ambiguity since no object could be an instance of both Abstract1 and Abstract2, since no class can extend multiple classes. For this reason, the Java SE language specifications allows this to be compiled.

ReubenBeeler
  • 142
  • 1
  • 10