3

In Typescript, there is this concept of a polymorphic return type this. https://www.typescriptlang.org/docs/handbook/advanced-types.html#polymorphic-this-types Example:

export abstract class Animal {
    private name: string;

    public setName(name: string): this {
        this.name = name;
        return this;
    }
}

export class Dog extends Animal {
    private breed: string;

    public setBreed(breed: string): this {
        this.breed = breed;
        return this;
    }
}

export class FluffyDog extends Dog {
    private fluffiness: number;
    public setFluffiness(fluffiness: number): this {
        this.fluffiness = fluffiness;
        return this;
    }
}

export class Main {
    constructor() {
        const dog: FluffyDog = new FluffyDog()
            .setName('Fluffy')
            .setFluffiness(10)
            .setBreed('Lab');
    }
}

Is there anything equivalent in Java? The best I have come up with is:

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

class Dog extends Animal<Dog> {
    private String breed;
    public Dog setBreed(String breed) {
        this.breed = breed;
        return this;
    }
}


class Main {
    static {
        Dog dog =  new Dog()
                .setName("Fluffy")
                .setBreed("Lab");
    }
}

OR this:

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

class Dog extends Animal {
    private String breed;
    public <T extends Dog> T setBreed(String breed) {
        this.breed = breed;
        return (T)this;
    }
}

class FluffyDog extends Dog {
    private Long fluffiness;
    public <T extends FluffyDog> T setFluffiness(Long fluffiness) {
        this.fluffiness = fluffiness;
        return (T)this;
    }
}


class Main {
    static {
        FluffyDog dog =  new FluffyDog()
                .<FluffyDog>setName("Fluffy")
                .setFluffiness(10L)
                .setBreed("Lab");
    }
}

The first seems to be only able to be subclassed once.
The second requires explicit type arguments in some situations.
Is there any way to return polymorphic this in Java?

  • 3
    There is no self-type in Java, so doing it with a recursive type parameter (`class Animal>`) is the best you got. The drawback of this approach is that there is nothing stopping a subclass from doing `class Dog extends Animal`. – marstran Oct 30 '18 at 17:08
  • 1
    For a little more information (e.g. the "getThis()-trick") have a look here: http://www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html#FAQ206 - the basic drawbacks and shortcomings that marstran mentioned are still true though. – Thomas Oct 30 '18 at 17:12
  • For the love of Pete don't use static initializers where a method would do. `class Main static {` ugh, this is terrible style. – markspace Oct 30 '18 at 17:29

2 Answers2

3

Two other options:

  1. Since order of setter methods don't matter, functionally, call them in bottom-up order. No casting needed, if you assign to variable first.

    final FluffyDog dog = new FluffyDog();
    dog.setFluffiness(10)
       .setBreed("Lab")
       .setName("Fluffy");
    
  2. Override inherited setter methods to change return type, using super to delegate to actual implementation:

    class Animal {
        private String name;
        public Animal setName(String name) {
            this.name = name;
            return this;
        }
    }
    
    class Dog extends Animal {
        private String breed;
        @Override
        public Dog setName(String name) {
            super.setName(name);
            return this;
        }
        public Dog setBreed(String breed) {
            this.breed = breed;
            return this;
        }
    }
    
    class FluffyDog extends Dog {
        private long fluffiness;
        @Override
        public FluffyDog setName(String name) {
            super.setName(name);
            return this;
        }
        @Override
        public FluffyDog setBreed(String breed) {
            super.setBreed(breed);
            return this;
        }
        public FluffyDog setFluffiness(long fluffiness) {
            this.fluffiness = fluffiness;
            return this;
        }
    }
    
    class Main {
        public static void main(String[] args) throws Exception {
            final FluffyDog dog = new FluffyDog()
                    .setName("Fluffy")
                    .setBreed("Lab")
                    .setFluffiness(10);
        }
    }
    
Andreas
  • 154,647
  • 11
  • 152
  • 247
2

With your first alternative you can still make two levels of inheritance. Admittedly its not very readable and understandable. Unfortunately generics were a bolt-on on Java added in 1.5, and a lot of the Java Bean patterns do not lend themselves that well to these kinds of chained methods. (You will notice that most setter methods return void in the traditional Java classes).

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

public class Dog<T extends Dog<T>> extends Animal<T> {
    private String breed;
    public T setBreed(String breed) {
        this.breed = breed;
        return (T) this;
    }
}

public class FluffyDog extends Dog<FluffyDog> {
    private Long fluffiness;
    public <T extends FluffyDog> T setFluffiness(Long fluffiness) {
        this.fluffiness = fluffiness;
        return (T) this;
    }
}

If you wanted to allow FluffyDog to be overridden and offer the same pattern then you will have to do the same thing again with the generic type like in the Dog class.

jbx
  • 21,365
  • 18
  • 90
  • 144