Générer les structures de données avec AutoValue

En Java comme dans d’autres langages, nous écrivons souvent des classes qui représentent des structures de données (appelées parfois objets métier, DTO, value objects, ou encore value types). Nous appliquons très souvent le principe d’encapsulation en exposant les valeurs à travers des getters et des setters quand la structure de données est mutable.

Mais implémentez-vous systématiquement la méthode equals de votre classe ? Et la méthode hashcode ? Si c’est le cas, equals et hashcode sont-elles cohérentes ? Et quand vous ajoutez un champ, pensez-vous à mettre à jour ces deux méthodes ? Il est pourtant important que ces méthodes soient correctement implémentées, faute de quoi on peut avoir des surprises en les insérant dans une HashMap ou un HashSet par exemple.

Je ne parle pas de toString qui est très rarement implémentée. Pourtant, c’est très pratique de pouvoir voir une représentation textuelle d’un objet.

La combinaison de equals et toString est de plus très intéressante dans les tests unitaires, elle permet de ne pas comparer les objets champ par champ mais de comparer directement objet à objet. La différence entre les représentations textuelles des deux objets fournies par toString permet rapidement de comprendre pourquoi le test est en échec.

Lombok

Il existe différents outils qui permettent de déléguer intégralement l’implémentation de ces méthodes. Le plus connu est sans doute Lombok. En se basant sur les annotation processors, il est capable de compléter l’implémentation d’une structure de données.

@Data
public class User {
    private final String id;
    private final String name;
    private final String mail;
    private final Date registrationDate;
}

Si Lombok est présent dans le classpath au moment de la compilation, il se déclenche automatiquement et vient enrichir la version compilée de cette classe de façon à ce que le code suivant compile :

User user = new User("1234", "Foo Bar", "foo@bar.com", new Date());
System.out.println(user.getName());

Lombok implémente également automatiquement les méthodes equals, hashcode et toString. Mais derrière toute cette magie, Lombok présente une faiblesse qui est assez pénalisante. En modifiant la sortie du compilateur, il va au delà du cadre supporté par les annotation processors et le compilateur Java. Cela a pour conséquences qu’il est nécessaire d’installer une extension dans son IDE préféré pour que ce dernier voie toute la partie implicite du code qui ne figure pas dans le code source. De la même façon, Lombok ne peut pas être facilement utilisé à l’intérieur d’une application Play Framework.

AutoValue

AutoValue est un composant du projet Auto de Google qui vise à fournir des outils basés sur les annotations processors et la génération de code.

AutoValue s’attaque au même problème que Lombok. Il le fait de manière un peu moins magique, mais il reste dans le cadre supporté par les annotations processors, ce qui fait qu’il est compatible avec tous les outils Java.

La principale différence avec Lombok réside dans le fait qu’il génère une classe supplémentaire plutôt que de modifier celle générée par le compilateur. L’idée est d’écrire une classe abstraite qui décrit la structure de l’objet et AutoValue se charge de générer l’implémentation correspondante. On écrit ainsi le code suivant :

@AutoValue
public abstract class User {
    public abstract String getId();
    public abstract String getName();
    public abstract String getMail();
    public abstract Date getRegistrationDate();
}

Si AutoValue se trouve dans le classpath au moment de la compilation, il générera une implémentation pour cette classe dans le répertoire dédié au code généré (target/generated-sources/annotations). Cette classe se situe dans le même package et porte le même nom préfixé, ici il s’agit de AutoValue_User.

Afin de masquer l’utilisation d’AutoValue, il est préférable de masquer l’existence de la classe AutoValue_User. Pour cela, il suffit d’ajouter une factory en static dans la classe et le tour est joué :

@AutoValue
public abstract class User {
    public abstract String getId();
    public abstract String getName();
    public abstract String getMail();
    public abstract Date getRegistrationDate();

    public static User build(String id, String name, String mail, Date registrationDate) {
        return new AutoValue_User(id, name, mail, registrationDate);
    }
}

AutoValue se charge de générer les getters, les méthodes equals, hashcode et toString. Il ne sait en revanche travailler qu’avec des objets immuables, mais c’est une bonne pratique qu’il nous oblige à respecter. De la même manière, par défaut il jette une NullPointerException si on construit l’objet avec une valeur nulle. Une annotation permet tout de même de lui demander d’accepter les valeurs nulles champ par champ.

Au final, AutoValue est plus restrictif que Lombok dans les fonctionnalités qu’il offre, mais c’est la plupart du temps suffisant. Un objet AutoValue peut même être utilisé pour la sérialisation JSON avec Jackson. Pour le parsing JSON, il faut simplement lui indiquer comment construire l’objet :

@AutoValue
public abstract class User {
    public abstract String getId();
    public abstract String getName();
    public abstract String getMail();
    public abstract Date getRegistrationDate();

    @JsonCreator
    public static User build(@JsonProperty("id")String id,
                             @JsonProperty("name") String name,
                             @JsonProperty("mail") String mail,
                             @JsonProperty("registrationDate") Date registrationDate) {
        return new AutoValue_User(id, name, mail, registrationDate);
    }
}

Dans sa prochaine version, AutoValue sera capable de générer des builders permettant de construire un objet ou de modifier un objet existant. L’exemple suivant est tiré de la documentation :

Animal dog = Animal.builder().name("dog").numberOfLegs(4).build();

J’ai découvert AutoValue à Devoxx France 2014 (voir la présentation sur Parleys). Je l’ai essayé et je l’ai immédiatement adopté. C’est lui qui génère maintenant toutes mes structures de données.

L’image d’en-tête provient de Flickr.

2 thoughts on “Générer les structures de données avec AutoValue”

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *