CodeGym /Cursos /JAVA 25 SELF /Immutability — inmutabilidad de las clases record

Immutability — inmutabilidad de las clases record

JAVA 25 SELF
Nivel 22, Lección 1
Disponible

1. Clases record e inmutabilidad

Inmutabilidad — es la propiedad de un objeto por la cual su estado no puede cambiar después de su creación. Dicho de otro modo: si el objeto ya fue creado, no puedes modificar sus datos internos. ¡Eso es! Como si estuviera grabado en piedra.

Imagina un billete de tren. En cuanto se imprime, no puedes cambiar la fecha ni el lugar de salida (bueno, salvo que hagas trucos con Photoshop). El billete es un objeto inmutable. Si quieres otro billete, compras uno nuevo.

En programación, a este tipo de objetos se les llama objetos inmutables. Protegen el programa de cambios accidentales y hacen el código más seguro y predecible.

Características de un objeto inmutable

  • Todos los campos del objeto son final (solo se pueden asignar una vez, normalmente en el constructor).
  • No hay setters (métodos que cambian los valores de los campos).
  • Todos los métodos que devuelven datos internos o bien devuelven copias, o bien los propios datos también son inmutables.

Las clases record en Java se inventaron precisamente para crear estructuras de datos inmutables de forma sencilla y sin dolor.

¿Por qué un record es inmutable?

  • Todos los componentes del record son final.
    Cuando declaras un record, el compilador convierte automáticamente todos sus campos en private final. Esto significa que después de crear el objeto no podrás cambiar sus campos.
  • No hay setters.
    En un record no puedes añadir un método setX(int x): el compilador no te permitirá cambiar el valor del campo después de crear el objeto.
  • El constructor asigna los valores una sola vez.
    Todos los valores se establecen únicamente en el momento de crear el objeto.

Ejemplo

public record Point(int x, int y) {}

Point p = new Point(5, 10);
// p.x = 7; // Error de compilación: el campo x tiene acceso private y es final
// p.x(7);  // Error: ¡no hay setter!
System.out.println(p.x()); // 5

Un intento de cambiar un campo o de llamar a un setter inexistente provoca un error de compilación. Java vigila estrictamente el contrato de inmutabilidad.

2. Ventajas de los objetos inmutables

Seguridad en entornos multihilo

En programas multihilo (¡que hoy son la mayoría!) los objetos inmutables son como un chaleco antibalas. Si el objeto no puede cambiar, distintos hilos pueden leerlo tranquilamente sin temer que alguien modifique los datos en ese mismo instante. No hace falta sincronizar el acceso ni preocuparse por condiciones de carrera.

Hecho: muchas clases en la biblioteca estándar de Java que se usan intensamente en escenarios multihilo o bien son inmutables, o bien están especialmente protegidas contra cambios.

Facilitan la comprensión del código

Si un objeto es inmutable, siempre tienes la certeza: lo pasas a otro método o clase y no cambiará «a tus espaldas». Esto facilita mucho la lectura y la depuración del código. No hace falta adivinar quién y dónde pudo cambiar un campo: ¡nadie pudo!

Útiles como claves en colecciones

Los objetos inmutables son perfectos para usarlos como claves en colecciones como HashMap o HashSet. ¿Por qué? Porque sus equals y hashCode dependen únicamente de campos que no cambian. Así, el objeto no se «perderá» en la colección debido a un cambio de estado.

Menos errores ocultos

Un objeto mutable es fácil de estropear si pasas su referencia donde no debes. Un objeto inmutable es como un libro impreso: nadie puede arrancar o reescribir una página.

3. Comparación con clases normales

Comparemos el comportamiento de una clase normal y una clase record. Como ejemplo, tomemos un modelo sencillo de un punto en el plano.

Clase normal (mutable)

public class PointClass {
    private int x;
    private int y;

    public PointClass(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() { 
        return x; 
    }
    public int getY() { 
        return y; 
    }

    public void setX(int x) { 
        this.x = x; 
    }
    public void setY(int y) { 
        this.y = y; 
    }
}

Puedes crear un objeto y luego cambiar su estado cuanto quieras:

PointClass p = new PointClass(1, 2);
p.setX(10); // ahora p.x es 10

Clase record (inmutable)

public record Point(int x, int y) {}

Creas un objeto y listo: será para siempre tal como fue creado:

Point p = new Point(1, 2);
// p.x = 10;   // ¡Error! No hay acceso al campo
// p.x(10);    // ¡Error! No hay setter

Tabla: comparación de comportamiento

Clase normal Clase record
Campos cualesquiera solo private final
Setters se pueden añadir no se pueden añadir
Mutabilidad mutable inmutable
Autogeneración no sí (equals, hashCode, toString)

4. Práctica: cómo aprovechar la inmutabilidad de las clases record

Probemos a aplicar una clase record en un ejemplo pequeño. Supón que tenemos una aplicación bancaria y queremos almacenar información sobre una transacción:

public record Transaction(String fromAccount, String toAccount, double amount) {}

Creamos un objeto:

Transaction t = new Transaction("12345", "67890", 1500.0);
System.out.println(t);
// Transaction[fromAccount=12345, toAccount=67890, amount=1500.0]

Intentemos «transferir» el dinero a otra cuenta:

// t.toAccount = "11111"; // ¡Error! El campo es final, no hay acceso
// t.toAccount("11111");  // ¡Error! No hay setter

Si necesitamos otra transacción, creamos un nuevo objeto:

Transaction t2 = new Transaction(t.fromAccount(), "11111", t.amount());

Importante: inmutable no significa «incómodo». Es simplemente otro estilo de trabajo: si necesitas un nuevo estado, creas un nuevo objeto.

5. Particularidades de la inmutabilidad: qué hay que tener en cuenta

¡La inmutabilidad no siempre es absoluta!

Un record garantiza que sus campos no cambiarán. Pero si un campo es una referencia a un objeto mutable (por ejemplo, un array o una clase normal), el contenido de ese objeto sí puede cambiar.

Ejemplo con array

public record DataHolder(int[] data) {}

int[] arr = {1, 2, 3};
DataHolder holder = new DataHolder(arr);
arr[0] = 99;
System.out.println(holder.data()[0]); // 99! ¡El array ha cambiado!

Conclusión: si quieres una inmutabilidad real, usa solo tipos inmutables (String, Integer, otros records, etc.) o haz copias de los objetos mutables dentro del constructor canónico del record. Por ejemplo:

int[] copy = Arrays.copyOf(data, data.length);

6. Cómo hacer tú mismo que una clase normal sea inmutable

Si quieres convertir una clase normal en immutable (inmutable), tendrás que hacerlo a mano:

  • Marcar todos los campos como private final,
  • No añadir setters,
  • Inicializar todos los campos solo a través del constructor,
  • Si un campo es un objeto mutable, hacer una copia defensiva (defensive copy).

Ejemplo

public final class User {
    private final String name;
    private final int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String name() { 
        return name; 
    }
    public int age() { 
        return age; 
    }
}

Estarás de acuerdo en que con un record esto se hace más fácil y más corto:

public record User(String name, int age) {}

7. Errores típicos al trabajar con clases record inmutables

Error n.º 1: intentar cambiar un campo después de la creación.
Los principiantes a menudo intentan escribir p.x = 42; o p.x(42); para un objeto record. Pero el compilador dirá de inmediato: «¡Así no! El campo es final, no hay setter».

Error n.º 2: usar objetos mutables como componentes del record.
Si añadiste al record un campo de tipo List, Map, array u otro objeto mutable, el propio record no te protegerá de cambios en el contenido de ese objeto. Por ejemplo, si tienes un record User(List<String> hobbies), alguien podría añadir o eliminar un elemento de la lista, y eso cambiaría el estado de tu objeto record. Para evitarlo, usa colecciones inmutables (List.copyOf, Collections.unmodifiableList) o haz copias de las colecciones dentro del constructor del record.

Error n.º 3: entender mal la inmutabilidad.
Algunos piensan que si un objeto es un record, está protegido de cualquier cambio. En realidad, si los campos son referencias a objetos mutables, su contenido puede cambiar, lo que provocará errores inesperados.

1
Tarea
JAVA 25 SELF, nivel 22, lección 1
Bloqueada
Un billete que no se puede reescribir 🎫
Un billete que no se puede reescribir 🎫
1
Tarea
JAVA 25 SELF, nivel 22, lección 1
Bloqueada
Datos de figuras históricas 🗿
Datos de figuras históricas 🗿
1
Tarea
JAVA 25 SELF, nivel 22, lección 1
Bloqueada
Movimiento de un objeto en el mapa del juego 🎮
Movimiento de un objeto en el mapa del juego 🎮
1
Tarea
JAVA 25 SELF, nivel 22, lección 1
Bloqueada
Seguimiento de lecturas de sensores 📊
Seguimiento de lecturas de sensores 📊
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION