3

In trying to structure my classes in a logical way, I discovered Java's ability to do recursive generics. It is almost exactly what I was looking for structure-wise, but I ran into a problem with abstract classes. I think Foo and Bar would be extremely confusing for this example, so I've named my classes relating to my actual project.

public abstract class GeneCarrier<T extends GeneCarrier<T>> {
    protected Gene<T> gene;
    //...
}

public class Gene<T extends GeneCarrier<T>> {
    //...
}

public abstract class Organism extends GeneCarrier<Organism>{
    //...
}

public class Human extends Organism {
    public void foo(){
        Gene<Human> g; // Bound mismatch: The type Human is not a valid substitute for the bounded parameter <T extends GeneCarrier<T>> of the type Gene<T>
    }
}

I thought that the problem might be with the definition of my abstract Organism class, but this also produced a similar error:

public abstract class Organism extends GeneCarrier<? extends Organism>{
    //...
}

Is there an inherent problem in trying to use an abstract class with recursive template definitions, or have I made a mistake in the class definitions?

wrongu
  • 560
  • 3
  • 13
  • There is no point in declaring `public abstract class GeneCarrier>` over just `public abstract class GeneCarrier`. There is no benefit. – newacct Mar 18 '14 at 06:10
  • The benefit is that it requires each subclass to be a GeneCarrier for itself. It disallows something like `class Dog extends GeneCarrier` – wrongu Mar 19 '14 at 12:29
  • "It disallows something like class Dog extends GeneCarrier" No it doesn't. Try it: `class Cat extends GeneCarrier` `class Dog extends GeneCarrier` – newacct Mar 20 '14 at 03:44

4 Answers4

3

Is there an inherent problem in trying to use an abstract class with recursive template definitions, or have I made a mistake in the class definitions?

It looks like you made a mistake. The recursive bound on Gene's type parameter T necessitates that a type argument of Human should mean that Human is a GeneCarrier<Human>. But it isn't - Human is a GeneCarrier<Organism>.

To implement this pattern correctly, the recursive type parameter should be propagated down the inheritance tree until it reaches what I like to call a "leaf" class, which in this case seems to be Human:

public abstract class Organism<T extends Organism<T>> extends GeneCarrier<T> {
    //...
}

public final class Human extends Organism<Human> {
    public void foo(){
        Gene<Human> g; // valid
    }
}

This solves the issue at hand but you should know the ups and downs of using "self-types" in Java (generally known as the Curiously Recurring Template Pattern). I go into detail about implementing this pattern and its pitfalls on this post: Is there a way to refer to the current type with a type variable?

In general I find that developers try to use "self-types" in order to implement a type-safe "copy" method on certain classes (which seems like the case here since your type names are gene-related). When that happens I always recommend trying to decouple the copying responsibility to a separate type, in order to avoid the added complexity of recursive generics. My answer here is an example.

Community
  • 1
  • 1
Paul Bellora
  • 54,340
  • 18
  • 130
  • 181
  • Yours is the best answer. – Sotirios Delimanolis Mar 16 '14 at 00:49
  • Very thorough answer! The last link you gave is particularly relevant because yes, these Genes have copy functions.. By that pattern, though, you are requiring a verb-er class for most of the gene's functions (Copier, Mutater, etc).. which could just as easily get out of hand. I still need to think more about that one. – wrongu Mar 16 '14 at 02:59
0

Through your inheritance hierarchy, Human extends GeneCarrier<Organism>, but Gene expects <T extends GeneCarrier<T>>. So the type variable T should be bound to Human, but it can't since Human does not extend GeneCarrier<Human>.

Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
0

The problem here is that Human extends Organism. And Organism is a GeneCarrier<Organism>. But according to the specification, the type parameter of Gene must be a GeneCarrier that has "itself" as a type parameter (and not an arbitrary supertype).

One solution would be to allow also supertypes: In this case, "Human extends Organism extends GeneCarrier" is allowed:

abstract class GeneCarrier<T extends GeneCarrier<? super T>> {
    protected Gene<T> gene;
}

class Gene<T extends GeneCarrier<? super T>> {}

abstract class Organism extends GeneCarrier<Organism>{}

class Human extends Organism {}

But presumably, this is not what you want.

An alternative could be to let Human not extend Organism, but GeneCarrier<Human> - in this case, the requirement of "having itself as a type parameter" would be fulfilled:

abstract class GeneCarrier<T extends GeneCarrier<T>> {
    protected Gene<T> gene;
}

class Gene<T extends GeneCarrier<T>> {}

abstract class Organism extends GeneCarrier<Organism>{}

class Human extends GeneCarrier<Human> {}

If this is not applicable in your case, you might want to explain the requirements more clearly. Other alternatives may be possible, like passing the type parameter through the Organism class as Organsim<T extends Organism<T>>, but ... you see that recursive generics tend to become confusing and counterintuitive, except for a few simple cases.

Marco13
  • 53,703
  • 9
  • 80
  • 159
  • Thanks for the two examples. The first is closer to what I think the templates should *mean*, though the second is certainly easier to understand. Paul gave a third option which is, in a sense, a compromise of the two levels of complexity. – wrongu Mar 16 '14 at 02:41
0

It's not clear what you are trying to do.

First of all, classes with bounds like class GeneCarrier<T extends GeneCarrier<T>> are not useful. You should almost always use class GeneCarrier<T>.

Your actual error results from the fact that Human extends Organism, which extends GeneCarrier<Organism>, and so Human cannot possibly extend GeneCarrier<Human>, and Gene requires its bound T to extend GeneCarrier<T>. It's unclear why class Gene has this requirement at all. You do not show any code for that class, so there doesn't seem to be any reason.

What bound you need very much depends on what operations you perform on the type T. For example, if all you do is pass the type T into a method of GeneCarrier<T> that takes type T, then a bound of T extends GeneCarrier<? super T> would suffice. And with this bound Gene<Human> would work fine. Or, perhaps even a bound of <T> will suffice; it's hard to tell without any code.

If it is actually required that T extends GeneCarrier<T> (which is not very likely), then Human must extend GeneCarrier<Human>. In this case, Organism cannot be defined the way it is. If you take Paul Bellora's suggestion of making Organism also parameterized, then it would look like this:

public abstract class GeneCarrier<T> {
    protected Gene<T> gene;
    //...
}

public abstract class Organism<T> extends GeneCarrier<T>{
    //...
}

public class Human extends Organism<Human> {
    public void foo(){
        Gene<Human> g;
    }
}
newacct
  • 119,665
  • 29
  • 163
  • 224