Introducción
En este tutorial, aprenderemos qué hace que un objeto sea inmutable, cómo lograr la inmutabilidad en Java y qué ventajas conlleva hacerlo. La inmutabilidad es un concepto fundamental en la programación de Java que puede contribuir significativamente a la calidad y estabilidad de tu código.
1. ¿Qué es un objeto inmutable?
Un objeto inmutable es un objeto cuyo estado interno permanece constante después de haber sido creado totalmente. Esto significa que la API pública de un objeto inmutable nos garantiza que se comportará de la misma manera durante toda su vida útil.
Un ejemplo clásico de un objeto inmutable en Java es la clase String
. Aunque su API parece ofrecer un comportamiento mutable con su método replace
, el String
original no cambia. Analicemos esto con un fragmento de código:
String name = "baeldung";
String newName = name.replace("dung", "----");
assertEquals("baeldung", name);
assertEquals("bael----", newName);
En este caso, a pesar de llamar al método replace
, el valor de name
sigue siendo “baeldung”. Esto demuestra cómo la API proporciona métodos de solo lectura y no incluye métodos que cambian el estado interno del objeto.
2. La palabra clave final
en Java
Antes de intentar lograr la inmutabilidad en Java, es importante hablar sobre la palabra clave final
. En Java, las variables son mutables por defecto, lo que significa que podemos cambiar el valor que contienen. Usando la palabra clave final
al declarar una variable, el compilador de Java no nos permitirá cambiar el valor de esa variable. Por ejemplo:
final String name = "baeldung";
name = "bael..."; // Esto generará un error de compilación
Sin embargo, es importante notar que final
solo impide que cambiemos la referencia del objeto que la variable apunta, no protege el estado interno del objeto al que hace referencia utilizando su API pública:
final List<String> strings = new ArrayList<>();
assertEquals(0, strings.size());
strings.add("baeldung");
assertEquals(1, strings.size()); // Esto generará un error
Este segundo assertEquals
fallará porque agregar un elemento a la lista cambia su tamaño, y por lo tanto, no se trata de un objeto inmutable.
3. Inmutabilidad en Java
Ahora que entendemos cómo evitar cambios en el contenido de una variable, podemos utilizar ese conocimiento para construir la API de objetos inmutables. Construir la API de un objeto inmutable requiere asegurarnos de que su estado interno no cambiará sin importar cómo utilicemos su API.
Un primer paso importante en la dirección correcta es utilizar final
al declarar sus atributos:
class Money {
private final double amount;
private final Currency currency;
// ...
}
Java nos garantiza que el valor de amount
no cambiará, lo cual es cierto para todas las variables de tipo primitivo. Sin embargo, en nuestro ejemplo, solo se nos garantiza que currency
no cambiará, por lo que debemos confiar en la API de Currency
para protegerse contra cambios.
La mayoría de las veces, necesitamos que los atributos de un objeto contengan valores personalizados, y el lugar para inicializar el estado interno de un objeto inmutable es su constructor:
class Money {
private final double amount;
private final Currency currency;
public Money(double amount, Currency currency) {
this.amount = amount;
this.currency = currency;
}
public Currency getCurrency() {
return currency;
}
public double getAmount() {
return amount;
}
}
Como hemos mencionado anteriormente, para cumplir con los requisitos de una API inmutable, nuestra clase Money
solo tiene métodos de solo lectura.
Un apunte sobre la reflexión: Usando la API de reflexión de Java, podemos romper la inmutabilidad y cambiar objetos inmutables. Sin embargo, la reflexión viola la API pública del objeto inmutable y, generalmente, deberíamos evitar hacer esto.
4. Ventajas de la inmutabilidad
Dado que el estado interno de un objeto inmutable permanece constante en el tiempo, podemos compartirlo de forma segura entre múltiples hilos. Además, podemos utilizarlo libremente, y ninguno de los objetos que lo referencian notará ninguna diferencia. Podemos decir que los objetos inmutables son libres de efectos secundarios.
Estas características hacen que los objetos inmutables sean especialmente útiles en entornos multihilo. La inmutabilidad evita problemas comunes como el acceso simultáneo no controlado a datos compartidos, lo que reduce la posibilidad de errores de sincronización.
Consejos prácticos para programadores de Java
- Siempre que sea posible, prefiera utilizar objetos inmutables. Esto mejora la seguridad del programa y facilita el razonamiento sobre su comportamiento.
- Utilice el patrón Builder si su objeto requiere muchos parámetros de construcción. Esto puede simplificar la creación de objetos complejos sin introducir mutabilidad.
- Aproveche las clases inmutables existentes en Java y bibliotecas de terceros. Por ejemplo, la clase
String
, las clases inmutables enjava.time
(comoLocalDate
yLocalTime
), y las clases de bibliotecas como Guava tienen muchas implementaciones de objetos inmutables. - Evite el uso de mutabilidad en objetos que se compartan entre hilos. Si un objeto necesita ser mutable, asegúrese de que se maneje apropiadamente para evitar condiciones de carrera.
- Sea consciente de la diferencia entre
final
y la inmutabilidad. No confunda el modificador de accesofinal
con la inmutabilidad; un objeto puede serfinal
pero aún así ser mutable.
Conclusión
Los objetos inmutables no cambian su estado interno a través del tiempo, son seguros para hilos y están libres de efectos secundarios. Debido a estas características, los objetos inmutables son especialmente útiles al trabajar en entornos multihilo. Dominar la creación y el uso de objetos inmutables en Java no solo hará que tu código sea más claro y seguro, sino que también te ayudará a evitar errores comunes relacionados con la mutabilidad y la programación concurrente.
Al implementar la inmutabilidad en los objetos Java, contribuirás a un código más robusto, fácil de mantener y menos propenso a errores. Si bien la creación de objetos inmutables puede requerir un poco más de trabajo al principio, las ventajas a largo plazo en términos de calidad del código y rendimiento valen la pena.