سؤال

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?

هل كانت مفيدة؟

المحلول

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.

نصائح أخرى

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>.

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.

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;
    }
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top