Come creare una classe immutabile in Java?

In Java l'immutabilità significa che una volta creato un oggetto il suo stato interno non può essere modificato. Le classi immutabili in Java offrono molti vantaggi come il debug facile della sicurezza dei thread e tutto il resto. In Giava tutto il classi wrapper (come Integer Boolean Byte Short) e la classe String è immutabile. Possiamo anche creare la nostra classe immutabile.

In questo articolo impareremo:

  • Cosa significa immutabilità
  • Perché è utile
  • Come creare la nostra classe immutabile
  • Perché la copia approfondita è importante
  • Quali sono le limitazioni dei tipi di record Java

Cos'è una classe immutabile?

Una classe immutabile è una classe i cui oggetti non possono essere modificati una volta creati. Se apportiamo qualsiasi modifica, il risultato sarà un nuovo oggetto. Questo metodo viene utilizzato in applicazioni simultanee.

Regole per la creazione di una classe immutabile

  • La classe deve essere dichiarata come finale in modo che le classi figlie non possano essere create.
  • I membri dati nella classe devono essere dichiarati privato pertanto non è consentito l'accesso diretto.
  • I membri dati nella classe devono essere dichiarati come finale in modo che non possiamo modificare il loro valore dopo la creazione dell'oggetto.
  • Un costruttore parametrizzato dovrebbe inizializzare tutti i campi eseguendo a copia profonda in modo che i membri dati non possano essere modificati con un riferimento a un oggetto.
  • La copia approfondita degli oggetti dovrebbe essere eseguita nei metodi getter per restituire una copia anziché restituire il riferimento all'oggetto effettivo.

Nota : Non dovrebbero esserci setter o, in termini più semplici, non dovrebbe esserci alcuna opzione per modificare il valore della variabile di istanza.


Esempio: implementazione di una classe immutabile

Student.java

Java
   // Java Program to Create An Immutable Class   import     java.util.HashMap  ;   import     java.util.Map  ;   // declare the class as final   final     class   Student     {      // make fields private and final      private     final     String     name  ;      private     final     int     regNo  ;      private     final     Map   <  String       String  >     metadata  ;      // initialize all fields via constructor      public     Student  (  String     name       int     regNo       Map   <  String       String  >     metadata  )     {      this  .  name     =     name  ;      this  .  regNo     =     regNo  ;      // deep copy of mutable object (Map)      Map   <  String       String  >     tempMap     =     new     HashMap   <>  ();      for     (  Map  .  Entry   <  String       String  >     entry     :     metadata  .  entrySet  ())     {      tempMap  .  put  (  entry  .  getKey  ()     entry  .  getValue  ());      }      this  .  metadata     =     tempMap  ;      }      // only provide getters (no setters)      public     String     getName  ()     {      return     name  ;      }      public     int     getRegNo  ()     {      return     regNo  ;      }      // return deep copy to avoid exposing internal state      public     Map   <  String       String  >     getMetadata  ()     {      Map   <  String       String  >     tempMap     =     new     HashMap   <>  ();      for     (  Map  .  Entry   <  String       String  >     entry     :     this  .  metadata  .  entrySet  ())     {      tempMap  .  put  (  entry  .  getKey  ()     entry  .  getValue  ());      }      return     tempMap  ;      }   }   

In questo esempio abbiamo creato una classe finale denominata Studente. Ha tre membri dati finali, un costruttore parametrizzato e metodi getter. Tieni presente che non esiste un metodo setter qui. Tieni inoltre presente che non è necessario eseguire una copia approfondita o la clonazione dei membri dati dei tipi wrapper poiché sono già immutabili.

Geeks.java:

Java
   import     java.util.HashMap  ;   import     java.util.Map  ;   public     class   Geeks     {      public     static     void     main  (  String  []     args  )     {      // create a map and adding data      Map   <  String       String  >     map     =     new     HashMap   <>  ();      map  .  put  (  '1'       'first'  );      map  .  put  (  '2'       'second'  );      // create an immutable Student object      Student     s     =     new     Student  (  'GFG'       101       map  );      // accessing data      System  .  out  .  println  (  s  .  getName  ());         System  .  out  .  println  (  s  .  getRegNo  ());         System  .  out  .  println  (  s  .  getMetadata  ());         // try to modify the original map      map  .  put  (  '3'       'third'  );      System  .  out  .  println  (  s  .  getMetadata  ());         // try to modify the map returned by getMetadata()      s  .  getMetadata  ().  put  (  '4'       'fourth'  );      System  .  out  .  println  (  s  .  getMetadata  ());         }   }   

Anche dopo aver modificato la mappa originale o restituita, lo stato interno dell'oggetto Student rimane invariato. Ciò conferma il concetto di immutabilità.

Produzione:

 GFG   
101
{1=first 2=second}
{1=first 2=second}
{1=first 2=second}


Limitazione del record Java con campi mutabili

Introdotto Java 14 documentazione . Questo è un modo chiaro e conciso per definire classi simili immutabili:

record Studente(String nome int regNo Map metadati) {}


Ma questo offre solo una superficiale immutabilità. Se la Mappa viene modificata esternamente lo stato interno del record cambia:

Mappa mappa = nuova HashMap <>();

map.put('1' 'primo');


Studente s = new Student(mappa 'ABC' 101);


// Cambia lo stato interno: NON sicuro

map.put('2' 'secondo');

s.metadata().put('3' 'terzo');

Nota : utilizza il record solo se tutti i campi sono di tipo immutabile come String int o altri record.