[vc_row][vc_column][vc_column_text]Un objeto es inmutable si su estado no puede cambiar luego de su construcción. Los objetos inmutables no exponen forma alguna de que otros objetos cambien su estado; los atributos del objeto son inicializados una sólo una vez dentro del constructor y nunca cambian.
En este artículo, definiremos los pasos para crear una clase inmutable en Java y revisaremos los errores de los programadores al crear una clase inmutable.[/vc_column_text][/vc_column][/vc_row][vc_row][vc_column][vc_column_text]
1. Usos de una clase inmutable
Hoy en día, la especificación «debe-tener» para toda aplicación de software es que se pueda distribuir y que se multi proceso. Las aplicaciones multi-proceso son un dolor de cabeza para los desarrolladores considerando que los desarrolladores requieren proteger el estado de los objetos de las modificaciones por parte de otros procesos concurrentes que requieren modificar el mismo objeto, para este propósito, los desarrolladores normalmente utilizan los bloques de Sincronización en aquellos lugares donde se modifica el estado del proceso.
Con las clases inmutables, los estados nunca se modifican; cada modificación del estado de los objetos resulta en una nueva instancia, por lo tanto cada hilo utiliza una instancia diferente y los desarrolladores no deben preocuparse sobre las modificaciones por procesos concurrentes.[/vc_column_text][/vc_column][/vc_row][vc_row][vc_column][vc_column_text]
2. Clases inmutables populares en Java
String es la clase inmutable más popular en Java. Cuando se inicializa el valor no puede ser modificado. Operaciones como trim(), substring(), replace() siempre devuelven una nueva instancia y no afecta la instancia actual, es por eso que siempre utilizamos trim() de la siguiente forma:
1 2 | String alex = "Alex"; alex = alex.trim(); |
Otro ejemplo del JDK son las clases de envoltura tales como: Integer, Float, Boolean … estas clases no modifican el estado, sin embargo crean una nueva instancia cada que se requiere modificar estas clases:
1 2 | Integer a =3; a += 3; |
Después de llamar a+=3, se crea una nueva instancia para guardar el valor 6 y la primera instancia se pierde.[/vc_column_text][/vc_column][/vc_row][vc_row][vc_column][vc_column_text]
3. Como se crea una clase Inmutable
A fin de crear una clase inmutable, debes seguir los siguientes pasos:
- Se debe hacer que la clase sea final, así ninguna otra clase puede heredar de ella.
- Se debe hacer a todos los atributos final, así estos serán inicializados una vez dentro del constructor y no se podrán modificar luego.
- No exponer los métodos de asignación (métodos set)
- Cuando exponga métodos que modifican el estado de la clase, siempre debe devolver una nueva instancia de la clase.
- Si la clase tiene objetos que son mutables:
- Dentro del constructor, debes estar seguro de utilizar una copia clonada de los argumentos y nunca configure su campo mutable a la instancia real pasada a través del constructor, esto es para evitar que los clientes que pasen el objeto lo modifiquen posteriormente.
- Debe estar seguro de siempre devolver una copia clonada del atributo y nunca devolver la instancia del objeto real.
[/vc_column_text][/vc_column][/vc_row][vc_row][vc_column][vc_column_text]
3.1 Clases Inmutables Simples
Sigamos los siguientes pasos y creemos nuestra clase inmutable (ImmutableStudent.java).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public final class ImmutableStudent { private final int id; private final String name; public ImmutableStudent(int id, String name) { this.name = name; this.id = id; } public int getId() { return id; } public String getName() { return name; } } |
La clase presentada es una clase inmutable simple que no tiene ningún objeto mutable y nunca expone los atributos de ninguna manera; esta clase se utiliza generalmente con propósitos de almacenamiento en caché.[/vc_column_text][/vc_column][/vc_row][vc_row][vc_column][vc_column_text]
3.2 Paso de Objetos Mutables a Clases Inmutables
Ahora, compliquemos un poco nuestro ejemplo, crearemos una clase mutable denominada Age y agregaremos esta como un atributo de ImmutableStudent:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | public class Age { private int day; private int month; private int year; public int getDay() { return day; } public void setDay(int day) { this.day = day; } public int getMonth() { return month; } public void setMonth(int month) { this.month = month; } public int getYear() { return year; } public void setYear(int year) { this.year = year; } } |
Agregamos este atributo a la clase inmutable:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | public final class ImmutableStudent { private final int id; private final String name; private final Age age; public ImmutableStudent(int id, String name, Age age) { this.name = name; this.id = id; this.age = age; } public int getId() { return id; } public String getName() { return name; } public Age getAge() { return age; } } |
Entonces agregamos un atributo mutable del tipo Age a nuestra clase inmutable y agregamos esta en el constructor.
Creemos un ejemplo sencillo en donde verificaremos que nuestra clase inmutables ImmutableStudent no es más inmutable:
1 2 3 4 5 6 7 8 9 10 11 | public static void main(String[] args) { Age age = new Age(); age.setDay(1); age.setMonth(1); age.setYear(1992); ImmutableStudent student = new ImmutableStudent(1, "Alex", age); System.out.println("Alex age year before modification = " + student.getAge().getYear()); age.setYear(1993); System.out.println("Alex age year after modification = " + student.getAge().getYear()); } |
Luego de ejecutar la prueba, tenemos la siguiente salida:
1 2 | Alex age year before modification = 1992 Alex age year after modification = 1993 |
A fin de corregir esto y hacer que nuestra clase sea inmutable nuevamente, seguimos la recomendación #5 de los pasos que mencionamos para la creación de objetos inmutables. Entonces modificamos el constructor a fin de clonar el argumento que pasamos de la clase Age y utilizamos la instancia de este objeto.
1 2 3 4 5 6 7 8 9 | public ImmutableStudent(int id, String name, Age age) { this.name = name; this.id = id; Age cloneAge = new Age(); cloneAge.setDay(age.getDay()); cloneAge.setMonth(age.getMonth()); cloneAge.setYear(age.getYear()); this.age = cloneAge; } |
Ahora, si ejecutamos nuestra prueba, tenemos la siguiente salida:
1 2 | Alex age year before modification = 1992 Alex age year after modification = 1992 |
Tal como puede observar, la edad de Alex nunca se ve afectada luego de la construcción y nuestra clase volvió a ser inmutable.[/vc_column_text][/vc_column][/vc_row][vc_row][vc_column][vc_column_text]
3.3. Devolviendo Objetos Mutables de Clases Inmutables
Sin embargo, nuestra clase aún no es completamente inmutable, propongamos el siguiente escenario:
1 2 3 4 5 6 7 8 9 10 11 12 | public static void main(String[] args) { Age age = new Age(); age.setDay(1); age.setMonth(1); age.setYear(1992); ImmutableStudent student = new ImmutableStudent(1, "Alex", age); System.out.println("Alex age year before modification = " + student.getAge().getYear()); student.getAge().setYear(1993); System.out.println("Alex age year after modification = " + student.getAge().getYear()); } |
Salida:
1 2 | Alex age year before modification = 1992 Alex age year after modification = 1993 |
De nuevo, de acuerdo al paso #4, cuando se devuelve atributos mutables desde objetos inmutables, debes devolver una instancia clone de esta y no una instancia real del atributo.
Entonces modificamos getAge() a fin de devolver el clone del objeto:
1 2 3 4 5 6 7 | public Age getAge() { Age cloneAge = new Age(); cloneAge.setDay(this.age.getDay()); cloneAge.setMonth(this.age.getMonth()); cloneAge.setYear(this.age.getYear()); return cloneAge; } |
Ahora la clase es completamente inmutable y no proporciona una forma o método para que otros objetos modifiquen el estado:
1 2 | Alex age year before modification = 1992 Alex age year after modification = 1992 |
[/vc_column_text][/vc_column][/vc_row][vc_row][vc_column][vc_column_text]
4. Conclusión
Las clases inmutables proporcionan muchas ventajas especialmente cuando se utilizan en forma correcta en procesos concurrentes. La única desventaja es que consume más memoria que las clases tradicionales desde que cada modificación implica una nueva instancia de la clase, pero, un desarrollador no debe desestimar el sobre consumo de memoria ya que es insignificante comparado con las ventajas de este tipo de clases.
Finalmente, un objeto es inmutable si este puede presentar solo un estado a los otros objetos, no importando donde y cuando se llama a los métodos. De ser así, es seguro para subprocesos con cualquier definición de seguridad de subprocesos.
Este artículo se encuentra en base a How to Create an Immutable Class in Java,[/vc_column_text][/vc_column][/vc_row]