Introduction aux immuables

1. Introduction

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

La bibliothèque se compose d'annotations et de processeurs d'annotations pour générer et travailler avec des objets immuables sérialisables et personnalisables.

2. Dépendances de Maven

Pour utiliser Immutables dans votre projet, vous devez ajouter la dépendance suivante à la section dépendances de votre fichier pom.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 l' étendue fournie .

La dernière version de la bibliothèque est disponible ici.

3. Immuables

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

La clé pour y parvenir est l'utilisation appropriée de l' annotation @ Value.Immutable . Il génère une version immuable d'un type annoté et préfixe son nom avec le mot clé Immutable .

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 note rapide - comme Immutables utilise le traitement des annotations, vous devez vous rappeler d'activer le traitement des annotations dans votre IDE.

3.1. Utilisation de @ Value.Immutable avec des classes abstraites et des interfaces

Créons une classe abstraite simple Person composée de deux méthodes d' accès abstraites 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 une classe ImmutablePerson nouvellement générée prête à l'emploi dans un répertoire target / generated-sources :

@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 livré avec mise en œuvre toString , hashcode , équivaut à des méthodes et avec un stepbuilder ImmutablePerson.Builder . Notez que le constructeur généré a un accès privé .

Pour construire une instance de la classe ImmutablePerson , nous devons utiliser le générateur ou la méthode statique ImmutablePerson.copyOf, qui peut créer une copie ImmutablePerson à partir d'un objet Person .

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 de john et créons un nouvel objet john43 :

ImmutablePerson john43 = john.withAge(43); 

Dans un tel cas, les affirmations 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 fournie 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 à la documentation d'Immutables.

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'accesseur non abstraite renvoyant une valeur fixe et l'annoter avec @ Value .

@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 qui sera ignorée par les implémentations equals , hashCode et toString .

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 champ auxiliaire :

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. Le @ Value.Immutable (Prehash = True) Annotation

Comme nos classes générées sont immuables et ne peuvent jamais être modifiées, les résultats de hashCode 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 du hashcode 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éthode hashCode () renvoie un hashcode précalculé généré lors de la construction de l'objet.

5. Conclusion

Dans ce rapide tutoriel, nous avons montré le fonctionnement de base de la bibliothèque Immutables.

Tout le code source et les tests unitaires de l'article se trouvent dans le référentiel GitHub.