20

With an abstract class I want to define a method that returns "this" for the subclasses:

public abstract class Foo {
    ...
    public <T extends Foo> T eat(String eatCake) {
        ...
        return this;
    }
}  

public class CakeEater extends Foo {}

I want to be able to do things like:

CakeEater phil = new CakeEater();
phil.eat("wacky cake").eat("chocolate cake").eat("banana bread");

Arguably banana bread would throw an IllegalArgumentException with the message "Not a cake!"

Sarabjot
  • 489
  • 1
  • 4
  • 13

4 Answers4

28
public abstract class Foo<T extends Foo<T>>  // see ColinD's comment
{
    public T eat(String eatCake) 
    {
        return (T)this;
    }
}

public class CakeEater extends Foo<CakeEater> 
{
    public void f(){}
}

Edit

There is no problem to require subclass behave in a certain way that's beyond what static typing can check. We do that all the time - pages and pages of plain english to specify how you write a subclass.

The other proposed solution, with covariant return type, must do the same - asking subclass implementers, in plain english, to return the type of this. That requirement cannot be specified by static typing.

irreputable
  • 44,725
  • 9
  • 65
  • 93
  • 2
    Should be `Foo>`. This works, though you can easily make a class that will blow up with a `ClassCastException` when you call `eat`, such as `class Bar extends Foo`. – ColinD Jul 19 '10 at 20:27
  • This is the solution I ended up using, but you'll notice that the return has an unchecked cast warning. While logically it makes sense that we can cast to T as this is a subclass of T. – Sarabjot Jul 19 '10 at 20:29
  • 4
    Yes. In a better world, Java could have a `This` type, which would be very useful in a lot of cases. – irreputable Jul 19 '10 at 20:30
  • `class FakeEater extends Foo { }` Now take notice of the warning. – Tom Hawtin - tackline Jul 19 '10 at 21:54
  • Tom, I didn't notice any warning. Maybe that's the point of your response, that there is no compile time warning. When calling FakeEater's eat there will be a a compile-time exception that the returning type is not of type FakeEater if it is being assigned to a FakeEater field. – Sarabjot Sep 01 '10 at 21:27
  • Could you store an instance of the subclass's class in the superclass? Maybe make in mandatory in the abstract class constructor. Calling clazz.cast(this) will then perform a typesafe cast back to the subclass type, won't it? – brabster May 29 '13 at 17:52
23

The tasteful approach from the client point of view (which is usually the one you want to take) is to use covariant return types which was added to support generics, as Michael Barker points out.

The slightly less tasteful, but more tasteful that a cast is to add a getThis method:

public abstract class Foo<T extends Foo<T>> {
    protected abstract T getThis();

    public T eat(String eatCake) {
        ...
        return getThis();
    }
}

public class CakeEater extends Foo<CakeEater> {
    @Override protected CakeEater getThis() {
        return this;
    }
}
Tom Hawtin - tackline
  • 145,806
  • 30
  • 211
  • 305
  • Hi @Tom. Why slightly less? yours is DRY, right? – Arash May 10 '22 at 17:21
  • 1
    @Arash It adds weirdo generics and a protected method to the interface, neither of which are tasteful. Though the alternative of overriding with a covariant return type does leave a load of rubbish of the form `@Override public CakeEater eat(String eatCake) { super.eat(SeatCake); return this; }`, which may be missed out when the base class is updated.. – Tom Hawtin - tackline May 11 '22 at 18:40
  • Thanks @Tom. Yes that's right. but i like generics with `getThis()` approach; it's safer ( I did not know till I read your answer ). Unfortunately i have to override because i couldn't find a way to do `child.eatParent().eatMyself()`. – Arash May 12 '22 at 08:00
9

I don't think you need generics Java 5 (and later) has covariant return types, e.g.:

public abstract class Foo {
    ...
    public Foo eat(String eatCake) {
        ...
        return this;
    }
}  

public class CakeEater extends Foo {

    public CakeEater eat(String eatCake) {
        return this;
    }
}
Michael Barker
  • 14,153
  • 4
  • 48
  • 55
  • If it's fully defined in the superclass then it creates redundant code to define it in the subclass; It could call the superclass but this seems unnecessary. It works if you cast this as type T and suppress warnings. – Sarabjot Jul 19 '10 at 20:18
  • It works if you cast this as type T and suppress warnings. The reason it should be return the type of the subclass is because: methodTakesCakeEater( (new CakeEater).eat("cake").eat("pie"); methodTakesCakeEater needs a CakeEater parameter. – Sarabjot Jul 19 '10 at 20:24
  • 6
    If a solution contains `@SuppressWarnings` it shouldn't be considered a solution. Just my two cents. – whiskeysierra Jul 19 '10 at 20:24
  • don't worry about warnings when you are doing edgy stuff. – irreputable Jul 19 '10 at 20:31
  • 1
    I do not think this is a good solution to the sketched problem because it requires one to override all methods in the subclass, hardly worth the effort. The idea is to have a fluent API that subclasses can leverage, not rewrite. – Peter Kriens Nov 08 '10 at 10:05
  • 2
    I also do not think this is "edgy" stuff requiring one to ignore or suppress warnings – Peter Kriens Nov 08 '10 at 10:05
  • Coming back to this from the future, the overridden `eat` method should call `super.eat` (and ignore the return value). Also perhaps add `@Override` - I'm not sure that was even possible for overridden abstract methods for versions of Java in common use when this answer was written. – Tom Hawtin - tackline May 11 '22 at 18:45
0

An approach I've used before to achieve similar behaviour is to have the subclass pass its type into a constructor of the (generified) parent type. By way of disclaimer I was generating the subclasses on the fly and inheritence was a bit of a cheat to keep my code generation simple, as always my first instinct is to try to remove the extends relationship altogether.

CurtainDog
  • 3,175
  • 21
  • 17