Introduzione agli immutabili

1. Introduzione

In questo articolo, mostreremo come lavorare con la libreria Immutables.

La libreria è composta da annotazioni e processori di annotazione per generare e lavorare con oggetti immutabili serializzabili e personalizzabili.

2. Dipendenze di Maven

Per poter utilizzare Immutables nel tuo progetto, devi aggiungere la seguente dipendenza alla sezione delle dipendenze del tuo file pom.xml :

 org.immutables value 2.2.10 provided 

Poiché questo artefatto non è richiesto durante il runtime, è consigliabile specificare l' ambito fornito .

La versione più recente della libreria può essere trovata qui.

3. Immutabili

La libreria genera oggetti immutabili da tipi astratti: Interfaccia , Classe , Annotazione .

La chiave per raggiungere questo obiettivo è l'uso corretto dell'annotazione @ Value.Immutable . Genera una versione immutabile di un tipo annotato e antepone al suo nome la parola chiave Immutable .

Se proviamo a generare una versione immutabile della classe denominata " X ", verrà generata una classe denominata "ImmutableX". Le classi generate non sono ricorsivamente immutabili, quindi è bene tenerlo a mente.

E una breve nota: poiché Immutables utilizza l'elaborazione delle annotazioni, è necessario ricordarsi di abilitare l'elaborazione delle annotazioni nel proprio IDE.

3.1. Utilizzo di @ Value.Immutable con classi e interfacce astratte

Creiamo una semplice classe astratta Person composta da due metodi di accesso astratti che rappresentano i campi da generare, quindi annotiamo la classe con l' annotazione @ Value.Immutable :

@Value.Immutable public abstract class Person { abstract String getName(); abstract Integer getAge(); }

Al termine dell'elaborazione dell'annotazione, possiamo trovare una classe ImmutablePerson nuova e pronta per l'uso in una directory 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 generata viene fornito con implementato toString , codice hash , è uguale a metodi e con uno stepbuilder ImmutablePerson.Builder . Si noti che il costruttore generato ha accesso privato .

Per costruire un'istanza della classe ImmutablePerson , è necessario utilizzare il builder o il metodo statico ImmutablePerson.copyOf, che può creare una copia ImmutablePerson da un oggetto Person .

Se vogliamo costruire un'istanza utilizzando il builder, possiamo semplicemente codificare:

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

Le classi generate sono immutabili, il che significa che non possono essere modificate. Se vuoi modificare un oggetto già esistente, puoi utilizzare uno dei metodi “ withX ”, che non modificano un oggetto originale, ma creano una nuova istanza con un campo modificato.

Aggiorniamo l' età di john e creiamo un nuovo oggetto john43 :

ImmutablePerson john43 = john.withAge(43); 

In tal caso, saranno vere le seguenti affermazioni:

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

4. Utilità aggiuntive

Tale generazione di classi non sarebbe molto utile senza la possibilità di personalizzarla. La libreria Immutables viene fornita con una serie di annotazioni aggiuntive che possono essere utilizzate per personalizzare l' output di @ Value.Immutable . Per vederli tutti, fare riferimento alla documentazione di Immutables.

4.1. L' annotazione @ Value.Parameter

L' annotazione @ Value.Parameter può essere utilizzata per specificare i campi, per i quali generare il metodo del costruttore.

Se annoti la tua classe in questo modo:

@Value.Immutable public abstract class Person { @Value.Parameter abstract String getName(); @Value.Parameter abstract Integer getAge(); }

Sarà possibile istanziarlo nel modo seguente:

ImmutablePerson.of("John", 42);

4.2. L' annotazione @ Value.Default

L' annotazione @ Value.Default consente di specificare un valore predefinito da utilizzare quando non viene fornito un valore iniziale. Per fare ciò, è necessario creare un metodo di accesso non astratto che restituisca un valore fisso e annotarlo con @ Value.Default :

@Value.Immutable public abstract class Person { abstract String getName(); @Value.Default Integer getAge() { return 42; } }

La seguente affermazione sarà vera:

ImmutablePerson john = ImmutablePerson.builder() .name("John") .build(); assertThat(john.getAge()).isEqualTo(42);

4.3. L' annotazione @ Value.Ausiliare

L' annotazione @ Value.Auxiliary può essere utilizzata per annotare una proprietà che verrà archiviata nell'istanza di un oggetto, ma verrà ignorata dalle implementazioni equals , hashCode e toString .

Se annoti la tua classe in questo modo:

@Value.Immutable public abstract class Person { abstract String getName(); abstract Integer getAge(); @Value.Auxiliary abstract String getAuxiliaryField(); }

Le seguenti affermazioni saranno vere quando si utilizza il campo ausiliario :

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

Poiché le nostre classi generate sono immutabili e non possono mai essere modificate, i risultati di hashCode rimarranno sempre gli stessi e possono essere calcolati solo una volta durante l'istanziazione dell'oggetto.

Se annoti la tua classe in questo modo:

@Value.Immutable(prehash = true) public abstract class Person { abstract String getName(); abstract Integer getAge(); }

Quando si ispeziona la classe generata, è possibile vedere che il valore hashcode è ora precalcolato e memorizzato in un campo:

@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; } } 

Il metodo hashCode () restituisce un hashcode precalcolato generato quando l'oggetto è stato costruito.

5. conclusione

In questo breve tutorial abbiamo mostrato il funzionamento di base della libreria Immutables.

Tutto il codice sorgente e gli unit test nell'articolo possono essere trovati nel repository GitHub.