Un record es una clase orientada a representar datos mediante un conjunto fijo de componentes.
Antes de los records, una clase de datos sencilla exigía bastante código repetitivo.
Quieres crear una clase simple para guardar los datos de un Producto (nombre y precio). Nada más. Solo datos.
Para hacerlo “bien” en Java clásico, tenías que escribir:
- Los atributos
private. - El Constructor.
- Los Getters (y quizás Setters).
- El método
equals()(para comparar). - El método
hashCode()(para usarlo en mapas). - El método
toString()(para que no salga basura al imprimir).
Resultado: 50 líneas de código para algo que debería ocupar 1. Es aburrido, ensucia el proyecto y es propenso a errores.
¡Se acabó! Con la llegada de los Records, Java soluciona esto de raíz.
¿Qué es un record?
Un record es un tipo especial de clase diseñado específicamente para ser un portador de datos inmutable.
La premisa es: “Si tú me dices qué datos quieres, yo (el compilador) generaré todo el código aburrido por ti”.
La comparativa: clase vs record
Mira esto. Es lo mismo, escrito de las dos formas.
Forma Clásica (POJO):
public class Producto {
private final String nombre;
private final double precio;
public Producto(String nombre, double precio) {
this.nombre = nombre;
this.precio = precio;
}
public String getNombre() { return nombre; }
public double getPrecio() { return precio; }
@Override
public String toString() { return "Producto[nombre=" + nombre + ", precio=" + precio + "]"; }
// ... Imagina aquí otras 20 líneas de equals() y hashCode() ...
}
Forma Moderna (Record):
public record Producto(String nombre, double precio) { }
Sí. Una sola línea. Eso es todo.
¿Qué ocurre bajo el capó?
Cuando compilas ese record, Java genera automáticamente por ti:
- Constructor canónico: Recibe todos los componentes (
nombre,precio). - Atributos privados y finales: Los datos son inmutables. No se pueden cambiar una vez creado el objeto.
- Métodos de acceso: ¡Ojo! No se llaman
getNombre(), se llaman igual que el atributo:nombre()yprecio(). equals()yhashCode(): Implementados perfectamente para comparar por valor (dos productos con el mismo nombre y precio serán iguales).toString(): Una implementación bonita que imprime los valores.
public static void main(String[] args) {
Producto p1 = new Producto("Portátil", 999.99);
Producto p2 = new Producto("Portátil", 999.99);
// Acceso a datos (Sin 'get')
System.out.println(p1.nombre());
// Comparación por contenido (Gracias al equals generado)
System.out.println(p1.equals(p2)); // true
// Imprimir
System.out.println(p1); // Salida: Producto[nombre=Portátil, precio=999.99]
}
Inmutabilidad: la restricción clave
Los Records están diseñados para ser Inmutables.
- No tienen Setters.
- Sus campos son
final.
Si quieres cambiar el precio de un producto, no puedes. Tienes que crear un nuevo record con el nuevo precio (usualmente copiando los datos del anterior).
Esto, que parece una limitación, es una bendición para la programación concurrente y para evitar bugs de estado. Los Records son ideales para DTOs (Data Transfer Objects), respuestas de APIs o claves de Mapas.
Personalizando un record
Aunque Java genera todo, a veces necesitamos añadir lógica extra.
Validación (constructor compacto)
Imagina que queremos prohibir que el precio sea negativo.
Los records tienen una sintaxis especial llamada Compact Constructor. No necesitas repetir los parámetros ni las asignaciones (this.x = x). Solo escribes la lógica de validación.
public record Producto(String nombre, double precio) {
// Constructor Compacto: Se ejecuta ANTES de asignar los valores
public Producto {
if (precio < 0) {
throw new IllegalArgumentException("El precio no puede ser negativo");
}
if (nombre == null || nombre.isBlank()) {
nombre = "Sin Nombre"; // Incluso podemos "normalizar" datos
}
}
}
Métodos extra
Puedes añadir tus propios métodos si lo necesitas.
public record Producto(String nombre, double precio) {
public double precioConIVA() {
return this.precio * 1.21;
}
}