Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bound Type Parameters in a Generic Method fail while an equivalent Generic Interface works, why?

Lets start with 3 interfaces. What they do isn't important. Just note that Car is parameterized (), while Foo and Bar are not.

interface Foo                               {void testFoo();}
interface Bar                               {void testBar();}
interface Car<A>                            {A    testCar();}

I want to 'composite' these interfaces, and this works just fine if I explicitly create the composites like this:

interface FooBar        extends Foo,Bar     {}
interface FooCar<A>     extends Foo,Car<A>  {}

However, I'd much prefer to implicitly composite the interfaces via the bounded type declarations of various methods. For example:

public <T extends Foo & Bar>        T   implicitFooBar()    {return null;}
public <X, T extends Foo & Car<X>>  T   implicitFooCar()    {return null;}

WORKs: The implictFooBar() method returns a type T which implements both the Foo and Bar interfaces (a composite if you will). A call to this method compiles, and I can avoid having to explicitly declare the FooBar interface:

// implicit composition of Foo and Bar, GOOD
FooBar implicitFooBar = implicitFooBar();
implicitFooBar.testFoo();
implicitFooBar.testBar();

FAILs: However, a call to implicitFooCar() fails to compile. The error message is "The method implicitFooCar() in the type GenericsTest is not applicable for the arguments ()" (I wrapped my test code in a class called GenericsTest.)

// implicit composition of Foo and Car<X>, FAIL!
//Compiler says "The method implicitFooCar() in the type GenericsTest is not applicable for the arguments ()"
FooCar<Number> implicitFooCar = implicitFooCar();   //compile error on method call
implicitFooCar.testFoo();                           
Number n2 = implicitFooCar.testCar();

The compiler error only shows when the type declaration is both compound and parameterized. For example, both of these compile and can be called just fine:

public <X>                      Car<X>  justCar()       {return null;}
public <X, T extends Car<X>>    T       implicitCar()   {return null;}

Question...

I suspect this has something to do with type erasure, but I'd like to understand the details of what's going on here. I've read the Oracle Generics Tutorials, but I'm not seeing what combination of rules the implicitFooCar() method violates, while implicitFooBar() and implicitCar() are fine. I'm looking for an academic style explanation, not just a work around.

Bonus

Interestingly enough, the following variant of calling the implicitFooCar() method works (no compiler errors.) This hints at why the other version doesn't work, but I've yet to connect those dots.

//variant... GOOD... but why?
implicitFooCar = this.<Number,FooCar<Number>>implicitFooCar();

Test Code (whole)

If you want to play with the code, here it is as a single class.

public class GenericsTest {

    public static interface Foo                             {void testFoo();}
    public static interface Bar                             {void testBar();}
    public static interface Car<A>                          {A testCar();}

    public static interface FooBar      extends Foo,Bar     {}
    public static interface FooCar<A>   extends Foo,Car<A>  {}



    public <X>                          Car<X>      justCar()           {return null;}

    public                              FooBar      explicitFooBar()    {return null;}
    public <T extends Foo & Bar>        T           implicitFooBar()    {return null;}

    public <X>                          FooCar<X>   explicitFooCar()    {return null;}
    public <X, T extends Foo & Car<X>>  T           implicitFooCar()    {return null;}

    public <X, T extends Car<X>>        T           implicitCar()       {return null;}


    public void test() {
        justCar().testCar();

        // explicit composition of Foo and Bar, GOOD
        FooBar explicitFooBar = explicitFooBar();
        explicitFooBar.testFoo();
        explicitFooBar.testBar();

        // explicit composition of Foo and Car<X>, GOOD
        FooCar<Number> explicitFooCar = explicitFooCar();
        explicitFooCar.testFoo();
        Number n1 = explicitFooCar.testCar();

        // implicit composition of Foo and Bar, GOOD
        FooBar implicitFooBar = implicitFooBar();
        implicitFooBar.testFoo();
        implicitFooBar.testBar();

        // implicit composition of Foo and Car<X>, FAIL!
        //Compiler says "The method implicitFooCar() in the type GenericsTest is not applicable for the arguments ()"
        FooCar<Number> implicitFooCar = implicitFooCar();   //compile error on method call
        implicitFooCar.testFoo();                           
        Number n2 = implicitFooCar.testCar();
        //variant... GOOD... but why?
        implicitFooCar = this.<Number,FooCar<Number>>implicitFooCar();

        // implicit (no composition) Car<X>, GOOD
        Car<Number> implicitCar = implicitCar();
        Number n3 = implicitCar.testCar();

    }

}

UPDATE

This compiles with javac version 1.8.0_60 (and _45 per comments), but Eclipse's (version 4.4.2.M20150204-1700) built in compiler ECJ is reporting the error mentioned above. I added an eclipse tag to this question, as this may be an EJC issue.

like image 265
allenru Avatar asked Oct 31 '22 15:10

allenru


1 Answers

First, to directly answer my question... It IS possible to declare a method with a bound parameterized type that combines two interfaces, one of which is parameterized itself.

The implicitFooCar() method from my example failed because of a bug in the Eclipse compiler ECJ used in Luna, version 4.4 of Eclipse. The same code compiled using the javac compiler (v 1.8.0_60) and in the next version of Eclipse, Mars (4.5).

Second, my desire to avoid explicit declarations of what I thought of as temporary or intermediary composite interfaces, was short sighted. Louis Wasserman pointed out that the method will at some point need to return an object that conforms to that composite specification, and thus I'll need an explicit version at that point.

like image 111
allenru Avatar answered Nov 15 '22 04:11

allenru