Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Method chaining + inheritance don’t play well together?

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?

like image 393
Jason S Avatar asked Jul 01 '09 14:07

Jason S


People also ask

What is method chaining Why is it bad?

It's merely syntactic sugar for where the next method acts on the return value of the previous method. No, it really is called method chaining regardless of the objects that are returned. Method chaining just means chaining multiple method calls into a single expression, rather than using multiple statements.

Why do we use method chaining?

Method chaining, also known as named parameter idiom, is a common syntax for invoking multiple method calls in object-oriented programming languages. Each method returns an object, allowing the calls to be chained together in a single statement without requiring variables to store the intermediate results.

Why do we use method chaining in Java?

Method Chaining is the practice of calling different methods in a single line instead of calling other methods with the same object reference separately. Under this procedure, we have to write the object reference once and then call the methods by separating them with a (dot.).


1 Answers

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();   } } 
like image 67
paulcm Avatar answered Sep 24 '22 02:09

paulcm