8

Given a generic interface

interface Foo<A, B> { }

I want to write an implementation that requires A to be a subclass of B. So I want to do

class Bar<A, B super A> implements Foo<A, B> { }
// --> Syntax error

or

class Bar<A extends B, B> implements Foo<A, B> { }
// --> illegal forward reference

But the only solution that seems to work is this:

class Bar<B, A extends B> implements Foo<A, B> { }

which is kind of ugly, because it reverses the order of the generic parameters.
Are there any solutions or workarounds to this problem?

Cephalopod
  • 14,632
  • 7
  • 51
  • 70

3 Answers3

5

Since this isn't possible in Java, try to think of Bar<B, A extends B> differently.

When you declare a variable for Bar, you're specifying the parent class first and then the child class. That's how Bar works. Don't think of it as being backwards - think of it as being forwards. The parent should naturally be specified before the child. This additional relationship you added is what drives the parameter order, not the underlying interface.

Erick Robertson
  • 32,125
  • 13
  • 69
  • 98
  • I appreciate that there's no way to get it done, but telling the OP he's thinking about it wrong and implying that it's probably "[hurting his] head" strikes me as terrible form. -1 for the least helpful answer I have ever seen here on SO. – Platinum Azure Jan 07 '11 at 18:28
  • 3
    @Platinum: Seriously? I find the many, many incorrect answers on here that are mistaken by ignorant people as correct and upvoted to be the least helpful. Anyway, since the ordering of type parameters the OP wants is not possible, I think this is about the most useful answer that could be provided as it goes beyond the simple "No" that I might have given. – ColinD Jan 07 '11 at 18:38
  • 3
    @Platinum - I certainly didn't read the answer as negatively as you read it (although I see how the last sentence _could_ be taken the wrong way). No where did he say the OP is thinking about it "wrong" or use another word with a possible negative connotation. The use of "backwards" is synonymous with the OP's use of "reverse". Sometimes what makes some things hard and not intuitive is not having suitable "mental model" to work with. I don't see anything wrong with offering a different way to look at things as an "answer". – Bert F Jan 07 '11 at 19:11
  • 1
    It could have been worded better... I would love to have seen it just start with, sorry, it's not possible in Java due to a language limitation. To me, because most of the answer is "think about it differently", it just feels like a mockery of the question. – Platinum Azure Jan 07 '11 at 19:15
  • 1
    @Platinum - This is probably just my natural sarcasm and aggression coming out. I've eased the wording according to your suggestions. – Erick Robertson Jan 07 '11 at 19:21
  • "Don't think of it as being backwards - think of it as being forwards." This kind of reminds me of doublethink, but I see your point. Still `Foo foo = new Bar` looks strange... – Cephalopod Jan 07 '11 at 19:52
  • @Erick Robertson: Thanks and sorry. (-1 becomes +1) – Platinum Azure Jan 07 '11 at 20:09
  • I'd actually be curious to know the real objects that you're working with that match this pattern. It seems a very specific case... – Erick Robertson Jan 07 '11 at 20:54
1

After seeing this question, I spent a little bit trying some different techniques that I thought might work. For example, building a generic interface ISuper<B,A extends B> and then having Bar<A,B> implements ISuper<B,A> (and a similar technique with a sub-class and extends rather than implements) but this just results in a type error, Bar.java:1: type parameter A is not within its bound. Likewise, I tried creating a method private <A extends B> Bar<A,B> foo() { return this; }; and calling it from the constructor, but this just results in the fun type error message Bar.java:2: incompatible types found : Bar<A,B> required: Bar<A,B>

So, I think that, unfortunately, the answer is no. Obviously, it's not the answer you were hoping for, but it seems that the correct answer is that this just isn't possible.

Keith Irwin
  • 5,628
  • 22
  • 31
1

It was pointed out already that there is neither a solution nor a nice workaround. Here is what I finally did. It only works for my special case, but you can take it as an inspiration if you run into similar problems. (It also explains why I ran into this problem)

First of all, there is this class (showing only the relevant interface):

class Pipe<Input, Output> {

    boolean hasNext();

    Input getNext();

    void setNext(Output o);

}

The Foo interface is actually

interface Processor<Input, Output> {

    process(Pipe<Input, Output> p);

}

and the class Bar should work like this

class JustCopyIt<Input, Output> implements Processor<Input, Output> {

    process(Pipe<Input, Output> p) {
       while (p.hasNext()) p.setNext(p.getNext());
    }

}

The easiest way would be to cast the values like this: p.setNext((Output) p.getNext()). But this is bad as it would allow to create an instance of JustCopyIt<Integer, String>. Calling this object would mysteriously fail at some point, but not at the point where the actual error is made.

Doing class JustCopyIt<Type> implements Processor<Type, Type> would also not work here, because then I am not able to process a Pipe<String, Object>.

So what I finally did was to change the interface to this:

interface Processor<Input, Output> {

    process(Pipe<? extends Input, ? super Output> p);

}

This way, a JustCopyIt<List> is able to process a Pipe<ArrayList, Collection>.

While this technically seems to be the only valid solution, it is still bad because it 1) only works for this special case, 2) required me to change the interface (which is not always possible) and 3) made the code of the other processors ugly.

Edit:
Reading Keiths answer again inspired me for another solution:

public abstract class Bar<A, B> implements Foo<A, B> {

    public static <B, A extends B> Bar<A, B> newInstance() {
        return new BarImpl<B, A>();
    }

    private static class BarImpl<B, A extends B> extends Bar<A, B> {
        // code goes here
    }

}

// clean code without visible reversed parameters
Bar<Integer, Object> bar1 = Bar.newInstance();
Bar<Object, Integer> bar2 = Bar.newInstance(); // <- compile error
Cephalopod
  • 14,632
  • 7
  • 51
  • 70
  • I like that. It's a neat work-around. I'm sure at some point someone else will read that code and find it confusing, but when used from the outside, it's a definite improvement. – Keith Irwin Jan 09 '11 at 08:52