mer 01 maggio 2024 - Lo Sviluppatore  anno VI

Java – Come creare una classe immutabile

Condividi

Vediamo in questo articolo come creare una classe immutabile in java. Ma cosa è un oggetto immutabile? Un oggetto immutabile è un oggetto che una volta istanziato e inizializzato non cambia mai il suo stato. Per esempio la classe String è un esempio di classe immutabile perchè una volta istanziata il suo valore non cambia.
Le classi immutabili sono utili ad esempio per essere inseriti in una cache in quanto non è necessario preoccuparsi delle modifiche del valore.
Un altro vantaggio delle classi immutabili è che sono intrinsecamente thread-safe, quindi non è necessario preoccuparsi della sicurezza dei thread in caso di ambiente multi-thread.

Per creare una classe immutabile occorre seguire i seguenti passi:

  1. Dichiarare la classe come final in modo che non possa essere estesa oppure rendere private il costruttore ed usare un factory method (metodo statico) per generare le istanze.
  2. Rendi private tutti i campi in modo che l’accesso diretto non sia consentito.
  3. Non fornire metodi setter per le variabili
  4. Rendi tutti i campi  final in modo che il valore possa essere assegnato solo una volta.
  5. Inizializza tutti i campi tramite un costruttore che esegue la copia profonda (deep copy).
  6. Nei metodi getter di campi di tipi mutabili restituire una copia piuttosto che restituire il riferimento all’oggetto reale.

Esempio

Ma passiamo ad un esempio che è sempre il miglior modo per comprendere bene le cose.

 

package it.losviluppatore.esempi;

import java.util.HashMap;
import java.util.Iterator;

public final class ClasseImmutabile {
 
 private final Integer id;
 
 private final String nome;
 
 private final HashMap<String,String> testMap;

// public ClasseImmutabile(int id, String nome, HashMap<String, String> testMap) {
//  System.out.println("Inizializzazione diretta");
//  this.id = id;
//  this.nome = nome;
//  this.testMap = testMap;
// }

 public ClasseImmutabile(int id, String nome, HashMap<String, String> inputMap) {
   System.out.println("Inizializzazione mediante Deep Copy");
 
   // normale init dei campi immutabili
   this.id = id;
   this.nome = nome;
 
   // init del capo di tipo mutabile (HashMap) mediante copia delle coppie chiave-valore estratti dalla mappa in input
   HashMap<String,String> tempMap = new HashMap<String,String>();
   String key;
   Iterator<String> it = inputMap.keySet().iterator();
   while(it.hasNext()){
    key=it.next();
    tempMap.put(key, inputMap.get(key));
   }
   this.testMap=tempMap;
}

 public Integer getId() {
   return id;
 }

 public String getNome() {
   return nome;
 }

 // Si restituisce una copia della HashMap in modo tale l'oggetto originale non possa essere modificato
 public HashMap<String, String> getTestMap() {
   return (HashMap<String, String>) testMap.clone();
 // return testMap;
 }
 
 public static void main(String[] args) {
   Integer i = 10;
   String str = "originale";
   HashMap<String,String> inputMap = new HashMap<String,String>();
   inputMap.put("1", "Uno");
   inputMap.put("2", "Due");
   System.out.println("Variabili locali passate in input: id="+i+", str="+str+", inputMap="+inputMap);
   ClasseImmutabile cx = new ClasseImmutabile(i, str, inputMap);
   System.out.println("Init oggetto");
   System.out.println("Output:");
   System.out.println(" cx.getId() = "+cx.getId());
   System.out.println(" cx.getNome() = "+cx.getNome());
   System.out.println(" cx.getTestMap() = "+cx.getTestMap());
 

   i = 20;
   str = "modificata";
   inputMap.put("3", "Tre");
   System.out.println("Modifico i valori delle variabili locali passate in input: id="+i+", str="+str+", inputMap="+inputMap);

   System.out.println("Output:");
   System.out.println(" cx.getId() = "+cx.getId());
   System.out.println(" cx.getNome() = "+cx.getNome());
   System.out.println(" cx.getTestMap() = "+cx.getTestMap());
 }
 

}

Eseguiamo la prima versione usando il costruttore che esegue la deep copy e il getter dell’oggetto HashMap che restituisce una copia del campo. Di seguito l’output:

Variabili locali passate in input: id=10, str=originale, inputMap={1=Uno, 2=Due}
Inizializzazione mediante Deep Copy
Init oggetto
Output:
 cx.getId() = 10
 cx.getNome() = originale
 cx.getTestMap(): = {1=Uno, 2=Due}
Modifico i valori delle variabili locali passate in input: id=20, str=modificata, inputMap={1=Uno, 2=Due, 3=Tre}
Output:
 cx.getId() = 10
 cx.getNome() = originale
 cx.getTestMap() = {1=Uno, 2=Due}

Come si vede i campi della nostra classe non hanno subito alcuna modifica.

Scommentiamo adesso il costruttore che inizializza i campi direttamente e il getTestMap() che restituisce direttamente il campo testMap, commentiamo l’altro costruttore  ed eseguiamo:

Variabili locali passate in input: id=10, str=originale, inputMap={1=Uno, 2=Due}
Inizializzazione diretta
Init oggetto
Output:
 cx.getId() = 10
 cx.getNome() = originale
 cx.getTestMap() = {1=Uno, 2=Due}
Modifico i valori delle variabili locali passate in input: id=20, str=modificata, inputMap={1=Uno, 2=Due, 3=Tre}
Output:
 cx.getId() = 10
 cx.getNome() = originale
 cx.getTestMap() = {1=Uno, 2=Due, 3=Tre}

Come si può vedere i due campi di tipo immutabile (Integer e String) sono rimasti come prima mentre la variabile di tipo HashMap (classe mutabile) è cambiata in quanto riferisce direttamente la variabile passata in input. Questa seconda versione non rende la nostra classe immutabile.

 

Lascia un commento

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.

Top