Search code examples
javaeclipsegenericscompilationjavac

Code compiling with eclipse but not with javac


The code below compiles without any error with Eclipse but generates an error with Javac. It seems to be a compiler error but I don't know which one is right.

I would like to point out that I know how to correct this error by changing the code to make it work with both, but that is not the current topic. I would just like to know whether it's a java or an eclipse problem.

I try with Intellij but I have the same javac error.

Sample code to reproduce this error:

import java.util.ArrayList;
import java.util.List;

public class A<T extends B> {
    protected List<C> list = new ArrayList<>();

    class C {}

    public void createIO() {
        A<? extends B> x = null;
        List<A<? extends B>.C> y = x.list;
    }
}

class B {
}

JVM:

openjdk version "13-BellSoft" 2019-09-17
OpenJDK Runtime Environment (build 13-BellSoft+33)
OpenJDK 64-Bit Server VM (build 13-BellSoft+33, mixed mode, sharing)

With Eclipse, I don't have any errors. With Javac, I have the error:

A.java:13: error: incompatible types: List<A<CAP#1>.C> cannot be converted to List<A<? extends B>.C>
        List<A<? extends B>.C> y = x.list;
                                    ^
  where CAP#1 is a fresh type-variable:
    CAP#1 extends B from capture of ? extends B

Solution

  • The Eclipse compiler should show a compiler error like javac does. See Eclipse bug 539105.

    javac behavior is correct and is aligned with the JLS:

    And if a generic class C<T> has a non-generic member class D, then the member type C<String>.D is a parameterized type, even though the class D is not generic.

    Thus, even though the inner class C is not generic, the:

    A<? extends B>.C
    

    is a parameterized type, where the type parameter of A is an unknown subtype of B. So, all rules on Generics apply to C as well in this context.

    Thus, to eliminate compiler error, the type of y can be declared as follows:

    List<? extends A<? extends B>.C> y = x.list;
    

    But keep in mind that this won't allow you to add items to the list y (see PECS).

    If it sounds complicated, try to think about it in scope of a simplified example:

    List<Integer> l1 = new ArrayList<>();
    List<Number > l2 = l1; // Error: incompatible types...
    
    List<? extends Number> l3 = l1; // OK
    

    Additional details:

    The example in the question can be minimized to:

    class A<T> {
        class C {}
        List<C> l1 = null;
        List<A<?>.C> l2 = l1; // Error: incompatible types...
        List<? extends A<?>.C> l3 = l1; // OK
    }
    

    List<A<?>.C> is a list of C of A of unknown type; C of arbitrary A can be added:

    class A<T> {
        class C {}
        C c = new C();
        void foo(List<A<?>.C> list) {
            list.add(new A<String>().c);
            list.add(new A<Number>().c);
        }
    }
    

    See also: