2

I have the below Java class (with nested classes/interfaces). When running the main method from within Eclipse (Version: 2019-09 R (4.13.0)) I get the following output:

java.version: 1.8.0_241
PageA.m3() called 

This is the command line used by Eclipse:

/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/bin/java -agentlib:jdwp=transport=dt_socket,suspend=y,address=localhost:52180 -javaagent:/Users/*****/eclipse/java-2019-09/Eclipse.app/Contents/Eclipse/configuration/org.eclipse.osgi/222/0/.cp/lib/javaagent-shaded.jar -Dfile.encoding=UTF-8 -classpath <a-list-of-.jar-files> _play.PageTest

When running the same code from within Intellij (IDEA 2019.3.3 (Community Edition)) I get the following output:

src/main/java/_play/PageTest.java:13: error: reference to m1 is ambiguous
                .m1(pageAorB -> ((SpecialPage<?>) pageAorB).m3());
                ^
  both method m1(Consumer<T#1>) in Page and method m1(Consumer<T#2>) in AbstractPage match
  where T#1,T#2 are type-variables:
    T#1 extends Page<T#1> declared in interface Page
    T#2 extends Page<T#2> declared in class AbstractPage

Why do I get this error in Intellij but not in Eclipse? Is there a way to solve this so it runs without error in Intellij?

Here is the Java class:

package _play;

import java.util.function.Consumer;
import java.util.function.Function;

public class PageTest {

    public static void main(String[] args) {
        System.out.println("java.version: " + System.getProperty("java.version"));

        new PageC()
                .m2(pageC -> (1 == 1 ? new PageA() : new PageB()))
                .m1(pageAorB -> ((SpecialPage<?>) pageAorB).m3());
    }

    public static interface Page<T extends Page<T>> {
        T m1(Consumer<T> c);
        <R> R m2(Function<? super T, ? extends R> f);
    }

    public static abstract class AbstractPage<T extends Page<T>> implements Page<T>{
        @Override
        public T m1(Consumer<T> c){
            c.accept(self());
            return self();
        }

        @Override
        public final <R> R m2(Function<? super T, ? extends R> f) {
            return f.apply(self());
        }

        abstract protected T self();
    }

    public static interface SpecialPage<T extends Page<T>> extends Page<T> {
        T m3();
    }

    public static class PageC extends AbstractPage<PageC> {

        @Override
        protected PageC self() {
            return this;
        }
    }

    public static class PageB extends AbstractPage<PageB> implements SpecialPage<PageB> {
        @Override
        public PageB m3() {
            System.out.println("PageB.m3() called");
            return this;
        }

        @Override
        public PageB self() {
            return this;
        }
    }

    public static class PageA extends AbstractPage<PageA> implements SpecialPage<PageA> {
        @Override
        public PageA m3() {
            System.out.println("PageA.m3() called");
            return this;
        }

        @Override
        public PageA self() {
            return this;
        }
    }
}

EDIT In this case the Page interface and AbstractPage class are in a library that the client can't change. but the client should be able to extend the Page interface.

etxalpo
  • 1,146
  • 1
  • 14
  • 25
  • Are you using Maven or Gradle? Can you try a command line? – Boris Feb 13 '20 at 15:09
  • Gradle. I'll try the command line... and report back. – etxalpo Feb 13 '20 at 15:12
  • Running from gradle command line (using a task with type: JavaExec) I get the same error as in Intellij – etxalpo Feb 13 '20 at 15:21
  • When you run it in Eclipse, what is the command line being used? It should be in the output window, can you add it to the question? Otherwise it's impossible to know how the project is configured to run in the IDE. – Boris Feb 13 '20 at 15:28
  • Updated question with the command line used by Eclipse. – etxalpo Feb 13 '20 at 15:46
  • 1
    The question is, is the Eclipse compiler (4.13) or `javac` (1.8.0_241) right (the _"command line used by Eclipse"_ is to run it, not to compile it and does not matter here). I would guess, since it's the same `T` here, Eclipse is right (`interface Page>` can be simplified to `interface Page` and `AbstractPage> implements Page` to `AbstractPage implements Page`, right?). @Boris @etxalpo – howlger Feb 13 '20 at 15:48
  • @etxalpo, please explain the `Page>` generics. – Boris Feb 13 '20 at 16:16
  • @Boris It is a recursive type bound. In this case it means that T must be an instance of Page. – etxalpo Feb 17 '20 at 14:16

1 Answers1

1

The question is, is the Eclipse compiler (ecj) of version 4.13 or javac 1.8.0_241 which is used by IntelliJ and Gradle by default right?

The error message of javac says it's ambiguous because in AbstractPage the type variable T refers to the type variable of AbstractPage (named T) and also refers to the different type variable of Page (also named T). But in fact, both type variable refer to the same type, not because they are named the same, but because AbstractPage implements Page<T>. It is not ambiguous and javac erroneously gives the compile error here.

As a workaround of this javac bug you can do one of the following:

  • Use the Eclipse compiler also in IntelliJ and Gradle
  • Rewrite the code in the main method using a variable of SpecialPage<? extends Page<?>> for the intermediate result so that javac need not infer it:

    SpecialPage<? extends Page<?>> pageAorB = new PageC().m2(pageC -> (1 == 1 ? new PageA() : new PageB()));
    pageAorB.m1(specialPage -> ((SpecialPage<?>) specialPage).m3());
    
howlger
  • 31,050
  • 11
  • 59
  • 99
  • 1
    While these issues involving type inference and overloading are typically too complex for many smart Java developers to precisely grasp, in this particular case `javac` itself gave us a hint, that there cannot be ambiguity between both `m1()` methods: `javac` didn't complain about the `@Override` annotation. – Stephan Herrmann Feb 13 '20 at 20:17
  • @howlger thx for the suggested workaround, but it does not apply to my use case. The Page and AbstractPage are in a library the client can't change. Question has been updated with this info. – etxalpo Feb 17 '20 at 14:25
  • @etxalpo Answer to this moving question has been updated as well. ;) – howlger Feb 17 '20 at 15:03