Chained methods are class methods that return the instance of the object so you can call another method on the same object. This article looks at the problems you can face with implementing classes with chained methods in Java when using inheritance, and how to solve them.

Introduction to Chaining methods

As I was doing a bit of research for this, I came across a number of terms for things that are often regarded as the same thing. Cascading methods is where the language allows multiple calls on the same object without having to specify the object again.

  object.add("SomeOtherValue");
    ..add("YetAnotherValue");
    ..someMethodOnObject();
    ..anotherMethod();

Notice the double periods before each subsequent method call which is the syntax in Dart for indicating that the method call returns the original object and not the result of the method call, even if the methods return an object themselves. This is usually a feature of the language (not available in Java) and is not something that can be implemented in your own code.

Method Chaining is the process by which a method returns an instance of an object for the purpose of calling another method on that object. Typically, the object being returned is the same instance that the method was called on.

   Person p = new Person()
     .firstName("Tim")
     .lastName("Smith")
     .age(31);

This shows how method chaining can also be used as a convenient way to build value types, especially in a builder class. Method chaining can also be used to implement Fluent Interfaces which provide developers with a mechanism for calling methods on classes such that they are almost as readable as English and can be used to build small DSLs.

  when(myObject.someMethod()).thenReturn(32);
  assertThat(myObject.someMethod()).isEqualTo(32).isNotNegative();

One issue with method chaining is that it removes the ability to return any other value from methods since it must return the object the method was invoked on.

Implementing Method Chaining

Let us take a simple value class where like many demonstrations of inheritance, we are capturing properties of a vehicle.

  public class VehicleProperties {

    private String registration;
    private int wheels = 4;

    public String getRegistration() {
      return registration;
    }

    public int getWheels() {
      return wheels;
    }

    public VehicleProperties registration(final String registration) {
      this.registration = registration;
      return this;
    }

    public VehicleProperties wheels(final int wheels) {
      this.wheels = wheels;
      return this;
    }
  }

This is an example of a class with a couple of chained methods that set a value on the instance and return a reference to the instance. You can invoke them using the following :

    VehicleProperties properties = new VehicleProperties()
      .registration("MAU3292")
      .wheels(4);

Note that since we return the object from the last chained method, we can use that result to assign the instance to our variable.

This works well when we have a single class, but what about when we have a class hierarchy? The parent class chained methods are already written to return itself, but invoking any chained methods on the subclass should return the subclass instance.

  public class TruckProperties extends VehicleProperties {
    
    private int maxLoadWeightInKg;

    public int getMaxLoadWeightInKg() {
      return maxLoadWeightInKg;
    }

    public TruckProperties maxLoadWeightInKg(final int maxLoadWeightInKg) {
      this.maxLoadWeightInKg = maxLoadWeightInKg;
      return this;
    }
  }

This looks ok, but when we come to use it, we have problems mixing the method calls to the subclass and the parent class:

  TruckProperties properties = new TruckProperties()
    .registration("EHW3829")
    .wheels(8)
    .maxLoadWeightInKg(1204);

This code won’t compile because the wheels() method returns an object of type VehicleProperties instead of the TruckProperties type which is what we constructed. This is because the VehicleProperties class doesn’t know anything about the truck properties or any other subclass and if it did, how would it know which one to return? The answer is to make the parent class generic, and make the generic type parameter extend itself:

  public class VehicleProperties<T extends VehicleProperties<?>> {

    //reference to self as the subclass type
    protected final T self;

    //make it protected to hide the magic
    protected VehicleProperties(final Class<T> selfClass) {
      this.self = selfClass.cast(this);
    }

    //return the generic type in our chained methods
    public T registration(final String registration) {
      this.registration = registration;
      return self;
    }

    public T wheels(final int wheels) {
      this.wheels = wheels;
      return self;
    }
  }

The fields and getters are the same, I just added a field to reference self and a constructor to pass the selfClass in. The value passed into the constructor should match the generic type argument used when constructing the subclass. We return the generic type instance from our chained methods by returning self.

In our subclass(es) we add the following constructor :

  public class TruckProperties extends VehicleProperties<TruckProperties> {

    private int maxLoadWeightInKg;

    public TruckProperties() {
      super(TruckProperties.class);
    }

    public TruckProperties maxLoadWeightInKg(final int maxLoadWeightInKg) {
      this.maxLoadWeightInKg = maxLoadWeightInKg;
      return self;
    }
    ...
  }

In our subclass constructor, we call the super constructor and pass in the generic type parameter class reference. This is the same class that we put as our type argument when we defined our subclass. This will be the type of object we want to return from both the parent and subclass and by passing it to the constructor, we are telling the parent class what type of object to return in the chained methods. We do this because Java generics are not reified which means we cannot determine the generic type argument at runtime. In the parent class, we cast this to the type we pass in.

In our chained methods, we return the field self and even made it protected in the parent class so it was accessible from subclasses. You could always use a protected getter method if you choose, but it is marked as final and cannot be changed.

By encapsulating the passing of the value to the parent constructor in the subclass we hide it from the user and they can then use the properties class as they would expect with the subclass instance being returned from all chained methods.

The pros and cons of Chained methods are subjective, but they can be very powerful and using inheritance via generics enables us to leverage that power even more. In the past, I’ve had several models which were based on a common parent class to which I’ve added chained methods. This kind of solution made it easy to leverage the benefits of inheritance.