Java Records
Usa Java records para crear clases de datos compactas e inmutables con accessors, equals y hashCode generados automáticamente.
Un record es una clase cuya única función es transportar datos. Declaras los campos una vez en la cabecera y el compilador genera el constructor, los accessors, equals, hashCode y toString por ti. Lo que antes requería 40 líneas de getters y código repetitivo es ahora una sola línea:
public record Point(int x, int y) {}Esa es la clase completa. new Point(3, 4), p.x(), p.y(), p.equals(other) y p.toString() funcionan y se comportan exactamente como esperarías.
Los records se finalizaron en Java 16 (tras versiones preliminares en 14 y 15). Esta página cubre qué genera el compilador, cómo validar y personalizar un record, las reglas que los records imponen, y cómo los records se combinan con el pattern matching.
Por qué existen los records
Antes de los records, cada "clase de datos" necesitaba un campo private final por componente, un constructor que los asignara, un getter por campo, equals y hashCode basados en valores, y un toString. La mayor parte de ese código era mecánico y fácil de hacer mal de forma sutil (olvidar un campo en equals, devolver el campo incorrecto desde un getter, copiar y pegar con error tipográfico). Los records colapsan todo eso en una cabecera, eliminando tanto la escritura como los errores.
Componentes y accessors
Los argumentos en la cabecera se llaman componentes. Cada uno se convierte en:
- Un campo
private finalcon el mismo nombre. - Un método accessor con el mismo nombre (no
getX()— simplementex()).
Point p = new Point(3, 4);
System.out.println(p.x() + ", " + p.y()); // 3, 4Los nombres coinciden porque los records se tratan de exponer datos de forma directa. No hay prefijo get porque no hay nada que ocultar.
equals, hashCode y toString generados
Dos records son iguales si y solo si son del mismo tipo de record y cada componente es igual:
Point a = new Point(3, 4);
Point b = new Point(3, 4);
System.out.println(a.equals(b)); // true
System.out.println(a); // Point[x=3, y=4]hashCode combina todos los componentes, por lo que los records funcionan correctamente como claves en HashMap y HashSet sin esfuerzo adicional.
Constructor compacto
Cada record tiene un constructor canónico — aquel cuyos parámetros coinciden con los componentes en orden. Por defecto el compilador lo escribe por ti (simplemente asigna cada parámetro a su campo). Cuando necesitas validación o normalización, no tienes que repetir esas asignaciones: escribe un constructor compacto, que no tiene lista de parámetros y se ejecuta antes de las asignaciones implícitas de campos:
public record Range(int low, int high) {
public Range {
if (low > high)
throw new IllegalArgumentException("low > high");
}
}También puedes reasignar las variables de parámetro dentro del constructor compacto — los valores finales son los que obtienen los campos:
public record Name(String first, String last) {
public Name {
first = first.strip();
last = last.strip();
}
}Agregar métodos
Los records pueden tener cualquier método que normalmente escribirías — simplemente no pueden tener campos de instancia adicionales (todo lo que respalda al record debe ser un componente):
public record Point(int x, int y) {
public double distanceTo(Point other) {
int dx = x - other.x;
int dy = y - other.y;
return Math.sqrt(dx * dx + dy * dy);
}
}Los campos estáticos y los métodos estáticos están permitidos. Los records también pueden implementar interfaces.
Lo que los records no pueden hacer
- Sin herencia. Un record extiende implícitamente
java.lang.Recordy esfinal— no puedes extender un record y un record no puede extender otra clase. - Sin estado mutable. Todos los componentes son
final. Si necesitas mutación, usa una clase normal. - Sin campos de instancia fuera de los componentes. No puedes añadir un
private int cache;adicional.
Estas restricciones son el punto clave. Un record promete "soy solo mis componentes, y nada más." Esa promesa es lo que hace que equals, hashCode y la serialización sean seguros de generar automáticamente.
Cuándo usar uno
Los records son adecuados cuando de otro modo escribirías una pequeña clase de datos inmutable — DTOs, objetos de configuración, tipos de retorno que agrupan algunos valores, tuplas en switches de pattern matching. No son adecuados cuando el tipo tiene identidad, posee estado mutable, o es la raíz de una jerarquía.
Un ejemplo práctico
Records en pattern matching
Como los componentes de un record son públicos y están ordenados, el compilador también sabe cómo descomponer un record. Un record pattern vincula cada componente a una variable en un solo paso, lo que hace que los records sean la forma natural para los datos sobre los que haces switch:
sealed interface Shape permits Circle, Rect {}
record Point(int x, int y) {}
record Circle(Point center, double r) implements Shape {}
record Rect(Point a, Point b) implements Shape {}
static String describe(Shape s) {
return switch (s) {
case Circle(Point c, double r) -> "circle r=" + r + " at " + c.x() + "," + c.y();
case Rect(Point a, Point b) -> "rect " + a + " to " + b;
};
}El patrón Circle(Point c, double r) verifica que s es un Circle y extrae sus componentes en una sola expresión. Consulta Java pattern matching para ver la característica completa, incluidos los patrones anidados.
Qué sigue
Los records bloquean una clase a un conjunto fijo de datos. El próximo capítulo presenta las clases selladas, que bloquean una jerarquía a un conjunto fijo de subtipos — la pieza que falta para modelar familias similares a los tipos de datos algebraicos cerrados en Java. Continúa con Java sealed classes.