tags:

views:

153

answers:

3

I thought I understood Generics pretty well, but apparently I didn't.

Here is the test case of the problem:

import java.util.ArrayList;

class Job<J extends Job<J,R>, R extends Run<J,R>> {}
class Run<J extends Job<J,R>, R extends Run<J,R>> {}

class Job2 extends Job<Job2,Run2> {}
class Run2 extends Run<Job2,Run2> {}

class RunList<J extends Job<J,R>, R extends Run<J,R>> extends ArrayList<R> {}

class Foo {
    // #1 problem
    public void test1(RunList<Job,Run> why) {}
    // #2 this doesn't work either
    public void test2(RunList<Job<Job,Run>,Run<Job,Run>> why) {}
    // #3 this works
    public void test3(RunList<Job2,Run2> why) {}
}

The compiler doesn't allow the test1 method above, saying that "Job" is not within its type bounds. I kinda sorta understand it --- Job as a raw type doesn't extend Job<Job,Run>, hence the error. In contrast, test3 works.

Now, the question is, how do I make this work? I've tried #2, but that doesn't work either. The problem I suppose is really similar with #1 --- Job<Job,Run> is not within the bounds because its type argument Job is a raw type.

Does anyone know how to make the type checker happy, other than resorting to the raw type? Or is it just not attainable in the Java type system?

+2  A: 

Maybe:

public <J extends Job<J, R>, R extends Run<J, R>> void test(RunList<J, R> why) {}
nairb774
But then I can't do why.add(new Job());
Kohsuke Kawaguchi
Rather, why.add(new Run());
Kohsuke Kawaguchi
RunList has elements of type R. This type is some subclass of Run. To be able to add to the list you need to know the exact subclass of Run that RunList is holding (or a subclass of that). Run is at best the same as R, and at worse a supertype of R.From the other perspective, presume test2's could be: "why.add(new Run());", if you write: RunList<Job2,Run2> rl = new RuRunList<Job2,Run2>(); test2(rl); Run2 run2 = rl.get(0); you will get a class cast exception on the get() line.
nairb774
IF you want to do a why.add(new Run());, then you need to declare your runlist as a specific type (as in Run2), or at least as a `? super R`.
Yishai
A: 

If you change the type parameter to ArrayList then you can add new Run();

Eugene Ho
Do you mean the raw type `ArrayList` without generics?
Willi
But that's even worse than using RunList as a raw type.
Kohsuke Kawaguchi
@Willi I should have been more specific. Useclass RunList<J extends Job<J,R>, R extends Run<J,R>> extends ArrayList<Run> {}This allows you too add Run objects as well as any objects of type R. I'm not sure whether this will affect the other parts of the RunList class.
Eugene Ho
A: 

You're right, not sure what I was thinking there! Your comment inspired me to think about it further and I tested it and the cause of the problem has something to do with the recursive definition of the type variables Job<J extends Job<J..., but I'm not entirely clear why. One solution, remove the 'use' of J and R from your definitions.

Longer answer:

import java.util.ArrayList;

class Job<J extends Job, R extends Run> {}
class Run<J extends Job, R extends Run> {}

class Job2 extends Job<Job2,Run2> {}
class Run2 extends Run<Job2,Run2> {}

class RunList<J extends Job, R extends Run> extends ArrayList<R> {}

class Foo {
    // #1 works now
    public void test1(RunList<Job,Run> why) {}
    // #2 works now too
    public void test2(RunList<Job<Job,Run>,Run<Job,Run>> why) {}
    // #3 still works
    public void test3(RunList<Job2,Run2> why) {}
    // #4 generic method
    public <J extends Job, R extends Run> void test4(RunList<J,R> why) {}

    public static void main(String[] args) {
        // Calling them even works...
        new Foo().test1(new RunList<Job,Run>());
        new Foo().test2(new RunList<Job<Job,Run>,Run<Job,Run>>());

        // Calling the generic method works too
        new Foo().test4(new RunList<Job,Run>());
        new Foo().test4(new RunList<Job<Job,Run>,Run<Job,Run>>());
        new Foo().test4(new RunList<Job2,Run2>());

        // ...sort of

        // This doesn't work
        //new Foo().test1(new RunList<Job<Job,Run>,Run<Job,Run>>());

        // This doesn't work
        //new Foo().test1(new RunList<Job2,Run2>());

    }
}
jaxzin
With regard to your statement of "The type boundary of 'extends' doesn't include the class/interface on the right-hand side; it only includes its subclasses/subinterfaces",I'm sorry, but I don't think so. List<Number> is assignable to List<T> where T extends Number.
Kohsuke Kawaguchi
Kohsuke, how do you plan on using the generics? Is your goal to enforce at compile time that you're RunList only contains subclasses of Run that are known to be compatible with a certain Job subclass? For instance, RunList<Job2,Run2> is valid but RunList<Job2,MyCustomRun> isn't because Job2 was only written to work with Run2?Also, is there anything wrong with making Job and Run interfaces?
jaxzin