¿Cómo crear una clase inmutable en Java?

En Java, la inmutabilidad significa que una vez que se crea un objeto, su estado interno no se puede cambiar. Las clases inmutables en Java brindan muchas ventajas, como la seguridad de los subprocesos, la fácil depuración y todo eso. En Java todos los clases contenedoras (como Integer Boolean Byte Short) y la clase String es inmutable. También podemos crear nuestra propia clase inmutable.

En este artículo vamos a aprender:

  • ¿Qué significa la inmutabilidad?
  • Por que es util
  • Cómo crear nuestra propia clase inmutable
  • Por qué es importante la copia profunda
  • ¿Cuáles son las limitaciones que tienen los tipos de registros Java?

¿Qué es una clase inmutable?

Una clase inmutable es una clase cuyos objetos no se pueden cambiar una vez creados. Si hacemos alguna modificación resultará en un nuevo objeto. Este método se utiliza en aplicaciones concurrentes.

Reglas para crear una clase inmutable

  • La clase debe declararse como final para que no se puedan crear clases secundarias.
  • Los miembros de datos de la clase deben declararse. privado por lo que no se permite el acceso directo.
  • Los miembros de datos de la clase deben declararse como final para que no podamos cambiar su valor después de la creación del objeto.
  • Un constructor parametrizado debe inicializar todos los campos realizando una copia profunda para que los miembros de datos no se puedan modificar con una referencia de objeto.
  • La copia profunda de objetos debe realizarse en los métodos getter para devolver una copia en lugar de devolver la referencia del objeto real.

Nota : No debería haber definidores o, en términos más simples, no debería haber ninguna opción para cambiar el valor de la variable de instancia.


Ejemplo: implementación de clase inmutable

Estudiante.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  ;      }   }   

En este ejemplo hemos creado una clase final llamada Alumno. Tiene tres miembros de datos finales, un constructor parametrizado y métodos getter. Tenga en cuenta que aquí no existe un método de establecimiento. También tenga en cuenta que no necesitamos realizar una copia profunda o clonación de miembros de datos de tipos de contenedor, ya que ya son inmutables.

Frikis.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  ());         }   }   

Incluso después de modificar el mapa original o devuelto, el estado interno del objeto Estudiante permanece sin cambios. Esto confirma el concepto de inmutabilidad.

Producción:

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


Limitación del registro Java con campos mutables

Java 14 introducido registro . Esta es una forma clara y concisa de definir clases similares inmutables:

registrar Estudiante (nombre de cadena int regNo Mapa metadatos) {}


Pero esto sólo ofrece una inmutabilidad superficial. Si el Mapa se modifica externamente el estado interno del registro cambia:

Mapa mapa = nuevo HashMap <>();

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


Estudiante s = nuevo Estudiante(mapa 'ABC' 101);


// Cambia el estado interno - NO seguro

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

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

Nota : Utilice el registro solo si todos los campos son de tipos inmutables como String int u otros registros.