views:

85

answers:

2

I want to have a function defined in a superclass that returns an instance of the subclass that is used to invoke the function. That is, say I have class A with a function plugh. Then I create subclasses B and C that extend A. I want B.plugh to return a B and C.plugh to return a C. Yes, they could return an A, but then the caller would have to either cast it to the right subtype, which is a pain when used a lot, or declare the receiving variable to be of the supertype, which loses type safety.

So I was trying to do this with generics, writing something like this:

class A<T extends A>
{
  private T foo;
  public T getFoo()
  {
    return foo;
  }
}
class B extends A<B>
{
  public void calcFoo()
  {
    foo=... whatever ...
  } 
}
class C extends A<C>
{
   public void calcFoo()
  {
    foo=... whatever ...
  } 
}

This appears to work but it looks pretty ugly.

For one thing, I get warnings on class A<T extends A>. The compiler says that A is generic and I should specify the type. I guess it wants me to say class A<T extends A<x>. But what would I put in for x? I think I could get stuck in an infinite loop here.

It seems weird to write class B extends A<B>, but this causes no complaints, so maybe that's just fine.

Is this the right way to do it? Is there a better way?

+1  A: 
class A<T extends A<T>> {

Or, I prefer:

class A<THIS extends A<THIS>> {

Like java.lang.Enum. It uglifies client code that just wants A, but has to write A<?>.

Note this does not in any way break LSP, and is GOOD OO.

Or you could just use covariant return types.

Tom Hawtin - tackline
In the case of concern to me right now, A will be abstract. I never want simply an A. Though that is certainly something to think about in the more general case.
Jay
I didn't even know covariant return types were now legal! I apparently missed that in the release notes when Java 5 came out. It doesn't work here because I didn't want to override the function, just have the same version in the superclass return different types depending on the generics. But it's good to know this feature exists!
Jay
@Jay Covariant return types were necessary to make work better. They are actually implemented by javac (not the jvm) by inserting synthetic bridge methods. A bit of a hack, but it doesn't mess the class file format up and helps a little with Java security too.
Tom Hawtin - tackline
I still disagree that this is GOOD OO - it may very well be good generic programming, but those 2 things aren't the same, and the designs and patterns they use are different.
Harper Shelby
A: 
Harper Shelby
The (possibly overridden) methods for subtypes can return a narrower type and not break LSP.
Tom Hawtin - tackline
It violates encapsulation and will make you miserable when you try to test it.
starblue
@Tom Hawtin: correct - if by "a narrower type" you mean "an instance of a subclass of the declared return type", and not "narrower" in the sense that int is narrower than long.
Harper Shelby
This does not violate LSP, because getFoo always returns an A -- its just that sometimes that A is a B or a C (a subclass of A)
Chris Dodd
RE "if the caller needs to know ... hierarchy is wrong". Hmm. So you're saying that casting and generics are ALWAYS bad programming? That if I write "Integer n=(Integer) someHashMap.get(x);", that's bad, because the caller should work with any object? Sorry, I don't buy that. It seems to me that it's highly desirable to write generic code that can work with many object types, and that when a caller wants to get more specific, it casts or uses generics to do so.
Jay
LSP basically says that anything true of the superclass should be true of the subclass -- not the other way around. A.getFoo() instanceof A is always true. B.getFoo() instanceof A is always true. If you're saying that any output that could ever be produced by a superclass must also be possible to be produced by a subclass, that would mean that the subclass must be identical to its superclasses. In which case, why bother having it?
Jay
@starblue: In what way? Why would this be harder to test than any other use of generics or casting?
Jay
@Jay Because it is looking at it's caller it could behave differently in a test than in normal use. Or it would be hard to even write a test, because of the need to pull in more context.
starblue
@Chris Dodd - yes it does, because A.getFoo() and B.getFoo() do not return the same object. If a B supports methods that an A does not, then I cannot call getFoo() on both an A and a B and reliably use the returned objects the same way. This design is *bad*. The caller of getFoo() shouldn't need to know if it's a B or a C. That's the point of having subclasses. Yes, a B is an A - but an A isn't necessarily a B. *That* is where the problem lies.
Harper Shelby
@Harper: I repeat my earlier post: What you're saying is apparently that generics and casts are ALWAYS bad programming practice. The entire Java collection package is a mistake. That's a pretty radical position! In my case, I can't imagine it causing a problem. I intend to make the superclass abstract, so every caller must use a subclass, and therefore it would know what type it's getting back. My reason for doing this is to ensure type-safety when passing the object as a parameter.
Jay
@Jay: Generics are not bad - generics should *prevent* casting. The Java non-generic collections (using Object for everything) are, IMHO, very bad. If I have callers that are calling getFoo() from the example above, they should be expecting a reference to the superclass, and *not* a particular subclass, because the public interface of the superclass tells them to expect an A. They *get* an A, whether it's a B or a C is, and should be, irrelevant **if they're calling getFoo()**. If B needs to expose a method that returns a B, then it should have its own public method, and not override getFoo().
Harper Shelby
@Harper: Well, I don't want to get into a protracted debate, but how is my example here different from the generic collections? If I say "class MyList extends ArrayList<Integer>", then MyList.get returns an Integer, not an Object. That's the whole point of generics. That's exactly what I'm trying to do here. Yes, I could dispense with the generics and have B.getFoo return an A. But in the context of my program, I know that it will always be a B and I need to use it as a B, so I would have to cast it. How would that be better? Okay, I could write a different function, a getBFoo or whatever. ...
Jay
(continued) in the subclass that would just return a B. But this function would be an identical copy of A.getFoo, except that the object it returns would be declared as B instead of A. I can't imagine how writing the exact same code twice except using a subclass instead of the original would be considered a virtue. That's what generics were to supposed to be for: So we didn't have to write exactly the same code multiple times. (Besides, a B IS an A, so if someone wrote "B b=new B(); A a=b.getFoo()", it would work just fine. No contract is broken.)
Jay