1. Ampliación de un record: métodos adicionales
¿Se pueden añadir métodos a un record?
¡Por supuesto! Un Record es como un piso ya reformado: las paredes y el suelo ya están hechos, no puedes cambiarlos, pero nadie te impide colocar los muebles a tu gusto. Dentro de un record puedes declarar métodos normales, métodos estáticos e incluso guardar constantes. Esto significa que la lógica de negocio no tiene por qué ir a clases «utilitarias» aparte: se puede integrar cuidadosamente en el propio record.
Ejemplo: método para calcular la distancia entre puntos
Supongamos que tenemos un record para un punto en el plano:
public record Point(int x, int y) {
// Método adicional
public double distanceTo(Point other) {
int dx = this.x - other.x;
int dy = this.y - other.y;
return Math.sqrt(dx * dx + dy * dy);
}
}
Ahora podemos hacer esto:
Point p1 = new Point(0, 0);
Point p2 = new Point(3, 4);
System.out.println(p1.distanceTo(p2)); // 5.0
Como ves, un record se puede «enriquecer» con sus propios métodos — ¡y es muy cómodo!
Ejemplo: método estático
public record Rectangle(int width, int height) {
public int area() {
return width * height;
}
public static Rectangle square(int size) {
return new Rectangle(size, size);
}
}
Ahora se puede crear un «cuadrado» con una sola llamada:
Rectangle r = Rectangle.square(5);
System.out.println(r.area()); // 25
2. Constructor compacto y validación de datos
¿Para qué sirve el constructor «compacto»?
El constructor canónico de un record se crea automáticamente y asigna los parámetros a los campos. Pero a veces queremos añadir comprobación de datos de entrada (por ejemplo, prohibir coordenadas negativas).
En una clase normal escribiríamos:
public class Point {
private final int x;
private final int y;
public Point(int x, int y) {
if (x < 0 || y < 0) throw new IllegalArgumentException();
this.x = x;
this.y = y;
}
// ...
}
En un record se puede declarar un constructor compacto — sin repetir la lista de parámetros y sin asignar explícitamente los campos (eso lo hace el compilador por nosotros).
Sintaxis del constructor compacto
public record Point(int x, int y) {
public Point {
if (x < 0 || y < 0) {
throw new IllegalArgumentException("Coordinates must be non-negative");
}
// No hace falta escribir: this.x = x; this.y = y;
}
}
- Los parámetros del constructor coinciden automáticamente con los componentes del record.
- La asignación this.x = x y this.y = y la realiza el compilador automáticamente tras ejecutar el cuerpo del constructor (o tras salir de él con éxito).
- Si se lanza una excepción, el objeto no se creará.
Ejemplo con comprobación
Point p1 = new Point(3, 5); // OK
Point p2 = new Point(-1, 2); // ¡Lanzará IllegalArgumentException!
¿Se puede declarar un constructor «normal»?
Sí. Si necesitas crear un constructor con otra lista de parámetros o con lógica adicional, decláralo explícitamente:
public record Range(int from, int to) {
public Range(int size) {
this(0, size); // llama al constructor principal
}
}
3. Limitaciones de los records
A diferencia de las clases normales, los record tienen una serie de limitaciones. Es importante tenerlo en cuenta para no sorprenderse con errores de compilación.
Solo componentes — ningún campo adicional no estático
En un record no se pueden declarar nuevos campos no estáticos:
public record Person(String name, int age) {
// int id; // Error de compilación. No se pueden añadir campos no estáticos.
}
Se pueden declarar campos y métodos estáticos:
public record Person(String name, int age) {
public static final String SPECIES = "Homo sapiens";
}
Un record siempre es final
Un Record no puede ser clase padre (no se puede heredar de él) y no puede heredar explícitamente de otra clase (salvo de la herencia implícita de java.lang.Record). Esto significa que un record es siempre una estructura «final».
public record User(String login) { }
// public class Admin extends User {} // Error: no se puede heredar de un record.
Se pueden implementar interfaces
Un Record puede implementar interfaces:
public interface Printable {
void print();
}
public record Invoice(int amount) implements Printable {
@Override
public void print() {
System.out.println("Importe: " + amount);
}
}
4. Ejemplos: records enriquecidos en tareas reales
Veamos algunos ejemplos prácticos donde los métodos adicionales y los constructores compactos hacen que un record sea realmente útil.
Record con método calculado
public record Circle(double x, double y, double radius) {
public double area() {
return Math.PI * radius * radius;
}
public double distanceTo(Circle other) {
double dx = x - other.x;
double dy = y - other.y;
return Math.sqrt(dx * dx + dy * dy);
}
}
Record con validación
public record Email(String value) {
public Email {
if (value == null || !value.contains("@")) {
throw new IllegalArgumentException("Email no válido: " + value);
}
}
}
Ahora no se puede crear un email no válido:
Email e1 = new Email("test@example.com"); // OK
Email e2 = new Email("not-an-email"); // Lanzará IllegalArgumentException
Record con métodos estáticos adicionales
public record Temperature(double celsius) {
public static Temperature fromFahrenheit(double fahrenheit) {
return new Temperature((fahrenheit - 32) * 5 / 9);
}
public double toFahrenheit() {
return celsius * 9 / 5 + 32;
}
}
Uso:
Temperature t = Temperature.fromFahrenheit(98.6);
System.out.println(t.celsius()); // 37.0
System.out.println(t.toFahrenheit()); // 98.6
5. Constructor compacto: matices y limitaciones
¿Cuándo usar el constructor compacto?
- Si necesitas comprobar la validez de los datos (validación).
- Si necesitas cambiar valores antes de almacenarlos (por ejemplo, redondear un número o convertir una cadena a mayúsculas).
- Si quieres evitar duplicar la lista de parámetros.
Detalles de funcionamiento
- En el constructor compacto no se pueden asignar explícitamente valores a los componentes (this.x = ...) — esto provocará un error de compilación, ya que el compilador realiza la asignación tras ejecutar el cuerpo del constructor.
- En el constructor compacto no se pueden cambiar los nombres de los parámetros: siempre coinciden con los nombres de los componentes del record.
Ejemplo: redondeo automático
public record Money(double amount) {
public Money {
amount = Math.round(amount * 100) / 100.0; // Redondeamos a 2 decimales
}
}
6. Práctica: mejoramos la aplicación didáctica
Supongamos que estamos implementando operaciones bancarias en una aplicación didáctica. Tenemos un record Transaction, que guarda el importe, el remitente y el destinatario.
public record Transaction(String from, String to, double amount) {
public Transaction {
if (amount <= 0) throw new IllegalArgumentException("El importe debe ser positivo");
if (from == null || to == null) throw new IllegalArgumentException("Los campos no pueden ser null");
}
public String description() {
return String.format("Transferencia %.2f de %s a %s", amount, from, to);
}
}
Uso:
Transaction t = new Transaction("Alice", "Bob", 150.0);
System.out.println(t.description()); // Transferencia 150.00 de Alice a Bob
Intentar crear una transacción no válida provocará un error:
Transaction t2 = new Transaction("Alice", "Bob", -10.0); // IllegalArgumentException
Tabla: qué se puede y qué no se puede en un record
| Se permite en un record | No se permite en un record |
|---|---|
| Métodos normales | Nuevos campos no estáticos |
| Métodos y campos estáticos | Extender otras clases |
| Implementar interfaces | Ser superclase de otros |
| Constructores compactos y normales | Modificar componentes después de crear el objeto |
| Sobrescribir métodos | Usar setters |
7. Errores típicos al trabajar con records con cuerpo no estándar
Error n.º 1: intentar añadir un campo no estático.
Los principiantes a menudo intentan añadir a un record «otro campo para la lógica interna» — por ejemplo, un contador o una caché. Esto no funcionará: el compilador dará un error de inmediato. Si necesitas almacenar estado adicional, probablemente un record no sea tu elección.
Error n.º 2: olvidar la validación en el constructor compacto.
Si quieres que el objeto sea siempre válido, realiza la comprobación en el constructor compacto. No confíes en que «el usuario no introducirá disparates».
Error n.º 3: intentar modificar un componente después de crear el objeto.
Los campos de un record son final — no se pueden modificar ni directamente ni a través de métodos. Si necesitas una estructura mutable, usa una clase normal.
Error n.º 4: duplicar lógica en métodos y en el constructor.
A veces se intenta duplicar la lógica de comprobación y la de cálculo tanto en métodos como en el constructor. Es mejor hacer toda la validación en el constructor y dejar los métodos para la lógica de negocio «limpia».
Error n.º 5: olvidar las limitaciones de herencia.
Un Record siempre es final — no se puede crear un descendiente de él. Si estás diseñando una jerarquía donde se necesitan subclases, usa clases normales.
GO TO FULL VERSION