Introducción
Este artículo proporciona una visión general de cómo crear una clase inmutable en la programación Java.
Un objeto es inmutable cuando su estado no cambia después de haber sido inicializado. Por ejemplo, String
es una clase inmutable y, una vez instanciado, el valor de un objeto String
nunca cambia. Aprende más sobre por qué la clase String
es inmutable en Java.
Como un objeto inmutable no puede ser actualizado, los programas necesitan crear un nuevo objeto para cada cambio de estado. Sin embargo, los objetos inmutables también tienen los siguientes beneficios:
- Una clase inmutable es buena para propósitos de almacenamiento en caché porque no tienes que preocuparte por los cambios de valor.
- Una clase inmutable es inherentemente segura para subprocesos, así que no tienes que preocuparte por la seguridad de los subprocesos en entornos multi-hilo.
Aprende más sobre la programación multi-hilo en Java y consulta las Preguntas de entrevista sobre Multi-Hilo en Java.
Crear una Clase Inmutable en Java
Para crear una clase inmutable en Java, es necesario seguir estos principios generales:
- Declarar la clase como
final
para que no pueda ser extendida. - Hacer que todos los campos sean
private
para que no se permita el acceso directo. - No proporcionar métodos setter para las variables.
- Hacer que todos los campos mutables sean
final
para que el valor de un campo solo pueda ser asignado una vez. - Inicializar todos los campos usando un método constructor que realice una copia profunda.
- Realizar clonación de objetos en los métodos getter para devolver una copia en lugar de devolver la referencia al objeto real.
La siguiente clase es un ejemplo que ilustra los conceptos básicos de inmutabilidad. La clase FinalClassExample
define los campos y proporciona el método constructor que utiliza una copia profunda para inicializar el objeto. El código en el método main
del archivo FinalClassExample.java
prueba la inmutabilidad del objeto.
Crear un nuevo archivo llamado FinalClassExample.java
y copiar el siguiente código:
import java.util.HashMap;
import java.util.Iterator;
public final class FinalClassExample {
// campos de la clase FinalClassExample
private final int id;
private final String name;
private final HashMap<String,String> testMap;
public int getId() {
return id;
}
public String getName() {
return name;
}
// Función Getter para objetos mutables
public HashMap<String, String> getTestMap() {
return (HashMap<String, String>) testMap.clone();
}
// Método constructor que realiza una copia profunda
public FinalClassExample(int i, String n, HashMap<String,String> hm){
System.out.println("Performing Deep Copy for Object initialization");
// La palabra clave "this" se refiere al objeto actual
this.id=i;
this.name=n;
HashMap<String,String> tempMap=new HashMap<String,String>();
String key;
Iterator<String> it = hm.keySet().iterator();
while(it.hasNext()){
key=it.next();
tempMap.put(key, hm.get(key));
}
this.testMap=tempMap;
}
// Prueba la clase inmutable
public static void main(String[] args) {
HashMap<String, String> h1 = new HashMap<String,String>();
h1.put("1", "first");
h1.put("2", "second");
String s = "original";
int i=10;
FinalClassExample ce = new FinalClassExample(i,s,h1);
// Imprime los valores ce
System.out.println("ce id: "+ce.getId());
System.out.println("ce name: "+ce.getName());
System.out.println("ce testMap: "+ce.getTestMap());
// Cambia los valores de la variable local
i=20;
s="modified";
h1.put("3", "third");
// Imprime los valores nuevamente
System.out.println("ce id after local variable change: "+ce.getId());
System.out.println("ce name after local variable change: "+ce.getName());
System.out.println("ce testMap after local variable change: "+ce.getTestMap());
HashMap<String, String> hmTest = ce.getTestMap();
hmTest.put("4", "new");
System.out.println("ce testMap after changing variable from getter methods: "+ce.getTestMap());
}
}
Compila y ejecuta el programa:
- javac FinalClassExample.java
- java FinalClassExample
Nota: Puede que recibas el siguiente mensaje al compilar el archivo: Nota: FinalClassExample.java utiliza operaciones no verificadas o inseguras
porque el método getter está utilizando una conversión no verificada de HashMap<String,String>
a Object
. Puedes ignorar la advertencia del compilador para los propósitos de este ejemplo.
Obtendrás la siguiente salida:
OutputPerforming Deep Copy for Object initialization
ce id: 10
ce name: original
ce testMap: {1=first, 2=second}
ce id after local variable change: 10
ce name after local variable change: original
ce testMap after local variable change: {1=first, 2=second}
ce testMap after changing variable from getter methods: {1=first, 2=second}
La salida muestra que los valores del HashMap no cambiaron porque el constructor utiliza una copia profunda y la función getter devuelve un clon del objeto original.
¿Qué sucede cuando no se utiliza la copia profunda y clonación?
Puedes realizar cambios en el archivo FinalClassExample.java
para mostrar lo que sucede cuando se utiliza la copia superficial en lugar de la copia profunda y se devuelve el objeto en lugar de una copia. El objeto ya no es inmutable y puede ser modificado. Realiza los siguientes cambios en el archivo de ejemplo (o copia y pega desde el ejemplo de código):
- Elimina el método del constructor que proporciona la copia profunda y agrega el método del constructor que proporciona la copia superficial resaltado en el siguiente ejemplo.
- En la función getter, elimina
return (HashMap<String, String>) testMap.clone();
y agregareturn testMap;
.
El archivo de ejemplo debería verse así ahora:
import java.util.HashMap;
import java.util.Iterator;
public final class FinalClassExample {
// campos de la clase FinalClassExample
private final int id;
private final String name;
private final HashMap<String,String> testMap;
public int getId() {
return id;
}
public String getName() {
return name;
}
// Función getter para objetos mutables
public HashMap<String, String> getTestMap() {
return testMap;
}
// Método del constructor realizando la copia superficial
public FinalClassExample(int i, String n, HashMap<String,String> hm){
System.out.println("Performing Shallow Copy for Object initialization");
this.id=i;
this.name=n;
this.testMap=hm;
}
// Probar la clase inmutable
public static void main(String[] args) {
HashMap<String, String> h1 = new HashMap<String,String>();
h1.put("1", "first");
h1.put("2", "second");
String s = "original";
int i=10;
FinalClassExample ce = new FinalClassExample(i,s,h1);
// imprimir los valores de ce
System.out.println("ce id: "+ce.getId());
System.out.println("ce name: "+ce.getName());
System.out.println("ce testMap: "+ce.getTestMap());
// cambiar los valores de la variable local
i=20;
s="modified";
h1.put("3", "third");
// imprimir los valores nuevamente
System.out.println("ce id after local variable change: "+ce.getId());
System.out.println("ce name after local variable change: "+ce.getName());
System.out.println("ce testMap after local variable change: "+ce.getTestMap());
HashMap<String, String> hmTest = ce.getTestMap();
hmTest.put("4", "new");
System.out.println("ce testMap after changing variable from getter methods: "+ce.getTestMap());
}
}
Compila y ejecuta el programa:
- javac FinalClassExample.java
- java FinalClassExample
Obtienes la siguiente salida:
OutputPerforming Shallow Copy for Object initialization
ce id: 10
ce name: original
ce testMap: {1=first, 2=second}
ce id after local variable change: 10
ce name after local variable change: original
ce testMap after local variable change: {1=first, 2=second, 3=third}
ce testMap after changing variable from getter methods: {1=first, 2=second, 3=third, 4=new}
El resultado muestra que los valores del HashMap se cambiaron porque el método del constructor utiliza una copia superficial, hay una referencia directa al objeto original en la función getter.
Conclusión
Has aprendido algunos de los principios generales a seguir cuando creas clases inmutables en Java, incluida la importancia de la copia profunda. Continúa tu aprendizaje con más tutoriales de Java.
Source:
https://www.digitalocean.com/community/tutorials/how-to-create-immutable-class-in-java