Introduction aux Immutables

Introduction aux Immutables

1. introduction

Dans cet article, nous montrerons comment travailler avec la bibliothèqueImmutables.

Immutables se compose d'annotations et de processeurs d'annotation permettant de générer et d'utiliser des objets immuables sérialisables et personnalisables.

2. Dépendances Maven

Pour utiliser les immuables dans votre projet, vous devez ajouter la dépendance suivante à la sectiondependencies de votre fichierpom.xml:


    org.immutables
    value
    2.2.10
    provided

Comme cet artefact n'est pas requis pendant l'exécution, il est donc conseillé de spécifier la portée deprovided.

La dernière version de la bibliothèque peut être trouvéehere.

3. Immuables

La bibliothèque génère des objets immuables à partir de types abstraits:Interface,Class,Annotation.

La clé pour y parvenir est l'utilisation correcte de l'annotation@Value.Immutable. It generates an immutable version of an annotated type and prefixes its name with the Immutable keyword.

Si nous essayons de générer une version immuable de la classe nommée «X», elle générera une classe nommée“ImmutableX”. Les classes générées ne sont pas immuables de manière récursive, il est donc bon de garder cela à l'esprit.

Et une remarque rapide - comme Immutables utilise le traitement des annotations, vous devez vous rappeler de l'activer pour le traitement des annotations dans votre IDE.

3.1. Utilisation de@Value.Immutable AvecAbstract Classes etInterfaces

Créons une simple classeabstractPerson composée de deux méthodes d’accesseurabstract représentant les champs à générer, puis annotons la classe avec l’annotation@Value.Immutable:

@Value.Immutable
public abstract class Person {

    abstract String getName();
    abstract Integer getAge();

}

Une fois le traitement des annotations terminé, nous pouvons trouver unnewly-generated ImmutablePerson class in a target/generated-sources directory prêt à l'emploi:

@Generated({"Immutables.generator", "Person"})
public final class ImmutablePerson extends Person {

    private final String name;
    private final Integer age;

    private ImmutablePerson(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    @Override
    String getName() {
        return name;
    }

    @Override
    Integer getAge() {
        return age;
    }

    // toString, hashcode, equals, copyOf and Builder omitted

}

La classe générée est fournie avec les méthodestoString,hashcode,equals implémentées et avec un stepbuilderImmutablePerson.Builder. Notez que le constructeur généré a un accèsprivate.

Afin de construire une instance de la classeImmutablePerson, nous devons utiliser le générateur ou la méthode statiqueImmutablePerson.copyOf, qui peut créer une copie deImmutablePerson à partir d'un objetPerson.

Si nous voulons construire une instance à l'aide du générateur, nous pouvons simplement coder:

ImmutablePerson john = ImmutablePerson.builder()
  .age(42)
  .name("John")
  .build();

Les classes générées sont immuables, ce qui signifie qu'elles ne peuvent pas être modifiées. Si vous souhaitez modifier un objet déjà existant, vous pouvez utiliser l'une des méthodes «withX», qui ne modifient pas un objet d'origine, mais créent une nouvelle instance avec un champ modifié.

Mettons à jour l'âge dejohn’set créons un nouvel objetjohn43:

ImmutablePerson john43 = john.withAge(43);

Dans un tel cas, les assertions suivantes seront vraies:

assertThat(john).isNotSameAs(john43);
assertThat(john.getAge()).isEqualTo(42);

4. Utilitaires supplémentaires

Une telle génération de classe ne serait pas très utile sans pouvoir la personnaliser. La bibliothèque Immutables est livrée avec un ensemble d'annotations supplémentaires qui peuvent être utilisées pour personnaliser la sortie de@Value.Immutable. Pour les voir tous, veuillez vous référer auxdocumentation des immuables.

4.1. L'annotation@Value.Parameter

L'annotation@Value.Parameter peut être utilisée pour spécifier des champs pour lesquels la méthode constructeur doit être générée.

Si vous annotez votre classe comme ceci:

@Value.Immutable
public abstract class Person {

    @Value.Parameter
    abstract String getName();

    @Value.Parameter
    abstract Integer getAge();
}

Il sera possible de l'instancier de la manière suivante:

ImmutablePerson.of("John", 42);

4.2. L'annotation@Value.Default

L'annotation@Value.Default vous permet de spécifier une valeur par défaut qui doit être utilisée lorsqu'aucune valeur initiale n'est fournie. Pour ce faire, vous devez créer une méthode d'accès non abstraite retournant une valeur fixe et l'annoter avec@Value.Default:

@Value.Immutable
public abstract class Person {

    abstract String getName();

    @Value.Default
    Integer getAge() {
        return 42;
    }
}

L'affirmation suivante sera vraie:

ImmutablePerson john = ImmutablePerson.builder()
  .name("John")
  .build();

assertThat(john.getAge()).isEqualTo(42);

4.3. L'annotation@Value.Auxiliary

L’annotation@Value.Auxiliary peut être utilisée pour annoter une propriété qui sera stockée dans l’instance d’un objet, mais sera ignorée par les implémentations deequals,hashCode ettoString.

Si vous annotez votre classe comme ceci:

@Value.Immutable
public abstract class Person {

    abstract String getName();
    abstract Integer getAge();

    @Value.Auxiliary
    abstract String getAuxiliaryField();

}

Les assertions suivantes seront vraies lors de l'utilisation du champauxiliary:

ImmutablePerson john1 = ImmutablePerson.builder()
  .name("John")
  .age(42)
  .auxiliaryField("Value1")
  .build();

ImmutablePerson john2 = ImmutablePerson.builder()
  .name("John")
  .age(42)
  .auxiliaryField("Value2")
  .build();
assertThat(john1.equals(john2)).isTrue();
assertThat(john1.toString()).isEqualTo(john2.toString());
assertThat(john1.hashCode()).isEqualTo(john2.hashCode());

4.4. L'annotation@Value.Immutable(Prehash = True)

Puisque nos classes générées sont immuables et ne peuvent jamais être modifiées, les résultats dehashCode resteront toujours les mêmes et ne pourront être calculés qu'une seule fois lors de l'instanciation de l'objet.

Si vous annotez votre classe comme ceci:

@Value.Immutable(prehash = true)
public abstract class Person {

    abstract String getName();
    abstract Integer getAge();

}

Lors de l'inspection de la classe générée, vous pouvez voir que la valeur dehashcode est maintenant précalculée et stockée dans un champ:

@Generated({"Immutables.generator", "Person"})
public final class ImmutablePerson extends Person {

    private final String name;
    private final Integer age;
    private final int hashCode;

    private ImmutablePerson(String name, Integer age) {
        this.name = name;
        this.age = age;
        this.hashCode = computeHashCode();
    }

    // generated methods

    @Override
    public int hashCode() {
        return hashCode;
    }
}

La méthodehashCode() renvoie unhashcode précalculé généré lors de la construction de l'objet.

5. Conclusion

Dans ce rapide didacticiel, nous avons montré le fonctionnement de base de la bibliothèqueImmutables.

Tout le code source et les tests unitaires de l'article se trouvent dans lesGitHub repository.