40

This question has been asked in a C++ context but I'm curious about Java. The concerns about virtual methods don't apply (I think), but if you have this situation:

abstract class Pet
{
    private String name;
    public Pet setName(String name) { this.name = name; return this; }        
}

class Cat extends Pet
{
    public Cat catchMice() { 
        System.out.println("I caught a mouse!"); 
        return this; 
    }
}

class Dog extends Pet
{
    public Dog catchFrisbee() { 
        System.out.println("I caught a frisbee!"); 
        return this; 
    }
}

class Bird extends Pet
{
    public Bird layEgg() {
        ...
        return this;
    }
}


{
    Cat c = new Cat();
    c.setName("Morris").catchMice(); // error! setName returns Pet, not Cat
    Dog d = new Dog();
    d.setName("Snoopy").catchFrisbee(); // error! setName returns Pet, not Dog
    Bird b = new Bird();
    b.setName("Tweety").layEgg(); // error! setName returns Pet, not Bird
}

In this sort of class hierarchy, is there any way to return this in a way that doesn't (effectively) upcast the the object type?

Community
  • 1
  • 1
Jason S
  • 184,598
  • 164
  • 608
  • 970

5 Answers5

59

If you want to avoid unchecked cast warnings from your compiler (and don't want to @SuppressWarnings("unchecked")), then you need to do a little more:

First of all, your definition of Pet must be self-referential, because Pet is always a generic type:

abstract class Pet <T extends Pet<T>>

Secondly, the (T) this cast in setName is also unchecked. To avoid this, use the "getThis" technique in the excellent Generics FAQ by Angelika Langer:

The "getThis" trick provides a way to recover the exact type of the this reference.

This results in the code below, which compiles and runs without warnings. If you want to extend your subclasses, then the technique still holds (though you'll probably need to genericise your intermediate classes).

The resulting code is:

public class TestClass {

  static abstract class Pet <T extends Pet<T>> {
    private String name;

    protected abstract T getThis();

    public T setName(String name) {
      this.name = name;
      return getThis(); }  
  }

  static class Cat extends Pet<Cat> {
    @Override protected Cat getThis() { return this; }

    public Cat catchMice() {
      System.out.println("I caught a mouse!");
      return getThis();
    }
  }

  static class Dog extends Pet<Dog> {
    @Override protected Dog getThis() { return this; }

    public Dog catchFrisbee() {
      System.out.println("I caught a frisbee!");
      return getThis();
    }
  }

  public static void main(String[] args) {
    Cat c = new Cat();
    c.setName("Morris").catchMice();
    Dog d = new Dog();
    d.setName("Snoopy").catchFrisbee();
  }
}
paulcm
  • 1,704
  • 1
  • 17
  • 17
  • the code gets cleaner this way, and I will take some time to read full Angelika article, thx vm! – Aquarius Power Feb 17 '15 at 14:41
  • 2
    `class Snake extends Pet {@Override protected Cat getThis() {return new Cat();}}` – user253751 Jul 18 '15 at 06:44
  • 3
    This gets a bit tricky though when you have non-abstract, non-final classes that are in the middle and need to create an instance. E.g., suppose you had a `static class Poodle extends Dog`, and changed `Dog` to be `static class Dog> extends Pet`. Now creating a raw instance of `Dog` would be difficult. – Marcus Nov 15 '15 at 00:09
  • is there some way it could possibly work with annonymous classes? I cant find a way to let it self reference for the generics T :/ – Aquarius Power Apr 30 '17 at 00:05
  • I created a thing that worked: `class PetAnnon extends Pet{}` and on every annonymous class I just use it like `new Pet{...` now methods like ` T get(Class cl){return (T)this.val;}` will work again. – Aquarius Power Apr 30 '17 at 00:29
20

How about this old trick:

abstract class Pet<T extends Pet>
{
    private String name;
    public T setName(String name) { this.name = name; return (T) this; }        
}

class Cat extends Pet<Cat>
{
    /* ... */
}

class Dog extends Pet<Dog>
{
    /* ... */
}
Rasmus Faber
  • 48,631
  • 24
  • 141
  • 189
  • +1, expressed more concisely than I did. But given how long java generics have been around, how old a trick could it be? – Steve B. Jul 01 '09 at 15:14
  • aha, I figured there would be something with generics, just didn't know what. Thanks! – Jason S Jul 01 '09 at 16:03
  • @Steve B: It is not old in Java (actually, I don't think I have seen it used in Java), but it has been used in C++ for a long time. – Rasmus Faber Jul 01 '09 at 16:17
  • Hmmm. Could you add an upcasting and downcasting example also? e.g. Pet> pet = c; ((Cat)pet).catchMice(); (do I have that right?) – Jason S Jul 01 '09 at 16:33
  • http://stackoverflow.com/questions/149336/practical-uses-for-the-curiously-recurring-template-pattern http://stackoverflow.com/questions/9138027/builder-pattern-for-polymorphic-object-hierarchy-possible-with-java – Ray Tayek Mar 11 '12 at 13:53
  • If you have a iterator then make it like this: `public Iterator iterator() {` That way you don't have to implement one for Cat and Dog but you will still be able to loop over Cat and Dog and actually getting a Cat or Dog instead of a Pet! – clankill3r Jun 06 '15 at 12:15
  • I'd very same thought in mind, every solution I checked on web suggests to make parent abstract/interface or something on same line. The issue is my parent class already exist, & is being used for many features in existing application(high usage if refactored). With minimal change I was looking to inherit the child class with added functionality, so I tried adding "protected final T self" in parent builder & returning "self" instead of "this" in all setters, with generics in definitinos >. But this requires refactoring in all usages. Is there any alternate way here? – Kiran A B Oct 29 '19 at 08:23
12

No, not really. You could work around it by using covariant return types (thanks to McDowell for the correct name):

@Override
public Cat setName(String name) {
    super.setName(name);
    return this;
}

(Covariant return types are only in Java 5 and above, if that's a concern for you.)

Anm
  • 3,291
  • 2
  • 29
  • 40
Michael Myers
  • 188,989
  • 46
  • 291
  • 292
5

It's a bit convoluted, but you can do this with generics:

abstract class Pet< T extends Pet > {
    private String name;

    public T setName( String name ) {
        this.name = name;
        return (T)this;
    }

    public static class Cat extends Pet< Cat > {
        public Cat catchMice() {
            System.out.println( "I caught a mouse!" );
            return this;
        }
    }

    public static class Dog extends Pet< Dog > {
        public Dog catchFrisbee() {
            System.out.println( "I caught a frisbee!" );
            return this;
        }
    }

    public static void main (String[] args){
        Cat c = new Cat();
        c.setName( "Morris" ).catchMice(); // error! setName returns Pet, not Cat
        Dog d = new Dog();
        d.setName( "Snoopy" ).catchFrisbee(); // error! setName returns Pet, not Dog
    }

}
Peter Hall
  • 53,120
  • 14
  • 139
  • 204
Steve B.
  • 55,454
  • 12
  • 93
  • 132
3
public class Pet<AnimalType extends Pet> {

private String name;
    public AnimalType setName(String name) {
       this.name = name; return (AnimalType)this; 
    }        
}

and

public class Cat extends Pet<Cat> {

    public Cat catchMice() {return this;}

    public static void main(String[] args) {
        Cat c = new Cat().setName("bob").catchMice();
    }

}

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
Paul Adamson
  • 2,011
  • 2
  • 18
  • 22