Records de Java: qué son y cómo usarlos

Los records son una alternativa a las clases introducida inicialmente en Java 14 aunque disponible de forma general a partir de Java 17, con la que se pueden crear rápidamente dataclases inmutables en tres líneas de código. Con los records Java te genera automáticamente getters, métodos toString, equals y hashCode y un constructor.

Los registros o records son una característica nueva de Java incorporada inicialmente como previa en la versión JDK 14, y posteriormente movida a estable en el JDK 17. Se trata de una implementación de las data-clases o del patrón Data Transfer Object, de otros lenguajes de programación. Son clases que se usan para almacenar valores y poder agruparlos en un único identificador, de manera fundamental.

Nosotros por lo general únicamente especificaremos qué atributos nos interesa que tenga un registro, y al compilar un registro, el compilador se ocupará de generar constructores, getters y métodos como toString(), equals() o hashCode() de forma automática. En definitiva, parte del atractivo de los registros es permitir conseguir algo parecido a lo que podemos hacer con clases tradicionales de Java, pero utilizando mucho menos código.

¿Qué es un registro?

Un record es una estructura superior igual que lo son las clases, los enumerados o las interfaces, por lo que lo más habitual será crear un record directamente sobre un archivo:

package example.records;

public record MiRecord() {

}

Hay varias cosas que desempaquetar sobre esta construcción.

En primer lugar, que como puedes ver, se trata de una estructura similar a la clase, pero en vez de usar public class usamos public record. Con esto le decimos al compilador que vamos a declarar un nuevo registro. Al igual que ocurre con las clases, luego podremos crear instancias de este registro.

En segundo lugar, esos paréntesis que aparecen junto al nombre del registro, en este caso MiRecord, que inicialmente están en blanco. Aquí dentro es donde incorporaremos ahora los atributos de este registro, para poderla utilizar.

¿Cómo declaramos los atributos a un registro?

Entre los paréntesis que van en la declaración de un record de Java, especificaremos los distintos atributos que queremos que formen parte del record, con sus tipos:

public record Cuenta(String nombre, String clave, boolean privilegiada) {

}

En este caso, al record Cuenta le hemos asociado ahora tres atributos: nombre y clave, ambos de tipo String, y privilegiado, de tipo boolean.

En el momento de compilar, Java convertirá este registro en una clase normal y corriente de la que podremos crear instancias posteriormente. Los atributos tendrán un significado especial en esta clase. Todos los atributos que especifiquemos entre paréntesis al crear el record serán convertidos en atributos de clase. Además, estos atributos van a ser de tipo private final, es decir, van a ser inmutables. Esta es una de las particularidades de los records: nos crean estructuras inmutables.

// Se crean a partir de los parámetros especificados en la declaración del record.
private final String nombre;
private final String clave;
private final boolean privilegiada;

En primer lugar, creará un constructor con todos los atributos del record como parámetro, cuyo comportamiento será establecerles un valor inicial. Es decir, que en el caso de nuestro record, nos generaría el siguiente código automáticamente:

// El constructor que nos generaría.
public Cuenta(String nombre, String clave, boolean privilegiada) {
  this.nombre = nombre;
  this.clave = clave;
  this.privilegiada = privilegiada;
}

En segundo lugar, para cada uno de los atributos se generará un método que tendrá como nombre el mismo nombre de cada uno de los atributos, que hace la vez de getter aunque no comienza por get. En este caso:

// Los “getters"
public String nombre() {
  return this.nombre;
}
public String clave() {
  return this.clave;
}
public boolean privilegiada() {
  return this.privilegiada;
}

No se generan setters. Como hemos visto antes, los atributos generados llevan el modificador final, por lo que no hay forma de cambiar el valor de uno de los atributos del registro. La única forma de hacer esto será derivar un record nuevo a partir de uno ya existente.

Por otra parte, también se generan métodos de utilidad como equals(), toString() y hashCode(). Hoy en día es de por sí muy común que los editores de textos nos ofrezcan generar automáticamente estos métodos, sobre todo para no tener que escribir a mano un algoritmo de hashCode() o para rellenar a mano el método toString(), pero que ya se ocupe el compilador de hacer esto por nosotros es de agradecer, y también permitirá generar métodos más óptimos en muchas ocasiones.

Crear métodos adicionales en un record

Es importante tener en cuenta que un record puede tener métodos adicionales. Los podemos introducir entre los corchetes que van junto a la declaración del record. Si bien no es esta la razón por la que los records deben llevar obligatoriamente corchetes (la razón real es por retrocompatibilidad y para que el intérprete de lenguaje del compilador no confunda un registro con otro tipo de construcciones), es el lugar correcto para ponerlo igualmente. En este caso, voy a crear dos métodos que permiten derivar información extra a partir de los atributos de nuestra clase:

public String identificador() {
  return “@“ + nombre;
}

public void tienePrivilegios() {
  If (this.privilegiada) {
    System.out.println("Tiene privilegios");
  } else {
    System.out.println("Es regular");
  }
}

Aprecia que los métodos los podemos usar tanto para devolver información derivada (como he hecho en identificador()), como para hacer código imperativo (como los println que he metido en el método tienePrivilegios()). Existe una limitación importante: no puedes crear setters. El siguiente código no va a compilar, y tiene sentido porque recordemos una vez más que los atributos que se generan en un record son de tipo final:

public void setClave(String clave) {
  // No compilará porque el atributo clave es final
  this.clave = clave;
}

Constructores auxiliares

También es posible crear constructores adicionales para un record. No obstante, existe la obligación de llamar en algún momento al constructor completo, porque todos los atributos del record deben recibir un valor inicial.

El constructor auxiliar de este record no va a funcionar, debido a que nos falta por proporcionarle los atributos.

public record Cuenta(String nombre, String clave, boolean privilegiada) {
  // No compila porque no se asignan valores para ningún atributo.
  public Cuenta() {
    System.out.println("Cuenta en blanco");
  }
}

Tampoco vale darle un valor inicial a unos atributos sí y a otros no. Todos tienen que tener su valor inicial:

public record Cuenta(String nombre, String clave, boolean privilegiada) {
  public Cuenta(String nombre, String clave) {
    // Se va a asumir que no es privilegiada.
    this(nombre, clave, false);
  }
}

Cómo utilizar un record

Un record se convierte en una estructura parecida a la de cualquier clase, por lo que a partir de ahora podemos crear instancias de un record si utilizamos sus constructores:

Cuenta c = new Cuenta("admin", "root", true);

Solamente ten en cuenta que si quieres sacar los atributos del record tendrás que usar los getters que se han generado para tal fin:

System.out.println(c.identificador());
c.tienePrivilegios();

Recuerda que como se generan métodos tipo toString(), hashCode() o equals(), puedes utilizarlos para imprimir a string registros o para compararlos con otras instancias:

System.out.println(c.toString());
If (c.equals(new Cuenta(admin, root, true))) {
  System.out.println("Iguales”);
} else {
  System.out.println("Diferentes);
}
Lista de reproducción
  1. 1
    Records de Java: qué son y cómo usarlos
    8 minutos
  2. 2
    ¿Cómo le pongo un setter a un record de Java?
    4 minutos
  3. 3
    Records vs Clases: ¿qué me interesa usar?
    7 minutos
  4. 4
    Pattern matching con records en Java
    9 minutos