Yann Moisan

Scala, Web, Linux…

Self-bound generic type

Ce billet porte sur les self-bound generic types. Mais que se cache derrière ce terme étrange. Rassurez-vous, aucun développeur n'a été blessé pour réaliser ce billet. Laissons le besoin émerger à travers un exemple. Le point de départ consiste à construire à l'aide de Builders deux objets, Child1 et Child2, ayant une propriété commune. Voici une première implémentation naive. Pour des raisons de clarté, nous n'affichons que le code nécessaire à la compréhension :

public class Child1Builder {
    private String parentProperty;
    private String child1Property;

    public Child1Builder setParentProperty(String parentProperty) {
        this.parentProperty = parentProperty;
        return this;
    }
    
    public Child1Builder setChild1Property(String child1Property) {
        this.child1Property = child1Property;
        return this;
    }


    public static void main(String[] args) {
        Child1Builder childBuilder = new Child1Builder();
        childBuilder.setParentProperty("parent").setChild1Property("childProperty");
    }
}

public class Child2Builder {
    private String parentProperty;
    private String child2Property;

    public Child2Builder setParentProperty(String parentProperty) {
        this.parentProperty = parentProperty;
        return this;
    }
    
    public Child2Builder setChild2Property(String child2Property) {
        this.child2Property = child2Property;
        return this;
    }


    public static void main(String[] args) {
        Child2Builder childBuilder = new Child2Builder();
        childBuilder.setParentProperty("parent").setChild2Property("childProperty");
    }
}

Nous allons maintenant refactorer pour supprimer le code dupliqué de la méthode setParentProperty.

public class ParentBuilder {
    public String parentProperty;

    public ParentBuilder setParentProperty(String parentProperty) {
        this.parentProperty = parentProperty;
        return this;
    }
}

public class Child1Builder extends ParentBuilder {
    private String child1Property;

    public Child1Builder setChild1Property(String child1Property) {
        this.child1Property = child1Property;
        return this;
    }


    public static void main(String[] args) {
        Child1Builder childBuilder = new Child1Builder();
        childBuilder.setParentProperty("parent").setChild1Property("childProperty");
    }
}

Malheureusement, ce code ne compile pas car la méthode setParentProperty renvoit une instance de ParentBuilder et il n'est pas possible de chainer l'appel à setChildProperty. La méthode setParentProperty a besoin de renvoyer le type de la classe dérivée. L'astuce consiste à utiliser une méthode getThis().

public abstract class ParentBuilder<T> {
    public String parentProperty;

    public T setParentProperty(String parentProperty) {
        this.parentProperty = parentProperty;
        return getThis();
    }
    
    public abstract T getThis();
}

public class Child1Builder extends ParentBuilder<Child1Builder> {
    private String child1Property;

    public Child1Builder setChild1Property(String child1Property) {
        this.child1Property = child1Property;
        return this;
    }

    public static void main(String[] args) {
        Child1Builder childBuilder = new Child1Builder();
        childBuilder.setParentProperty("parent").setChild1Property("childProperty");
    }

    @Override
    public Child1Builder getThis() {
        return this;
    }
}

Bien que ce code fonctionne, il est possible de l'améliorer. Le paramètre générique T doit être une sous-classe de ParentBuilder. Cela donne donc l'expression class ParentBuilder<T extends ParentBuilder<T>> qui peut paraitre étrange et montre la complexité des génériques qui se cache derrière leur apparente simplicité. C'est ce que l'on apelle un self-bound generic type.

Source :