Records (Java 16+)
Parte de la sección Programación Orientada a Objetos del Journey de Java de Coddy — lección 51 de 87.
Al crear clases sencillas que transportan datos, a menudo se escribe código repetitivo (boilerplate): campos privados, un constructor, getters, toString(), equals(), y hashCode(). Java 16 introdujo los records para eliminar este tedio.
Un record es una clase especial diseñada para almacenar datos inmutables. Se declara con la palabra clave record seguida de la lista de componentes:
record Person(String name, int age) { }
// Esa única línea proporciona automáticamente:
// - campos private final: name y age
// - un constructor: Person(String name, int age)
// - métodos de acceso: name() y age()
// - toString(), equals(), y hashCode()Usar un record es sencillo:
Person p = new Person("Alice", 30);
System.out.println(p.name()); // Alice
System.out.println(p.age()); // 30
System.out.println(p); // Person[name=Alice, age=30]Observa que los métodos de acceso no utilizan el prefijo get; simplemente se nombran igual que los campos. Los records son implícitamente final, por lo que no pueden ser extendidos. Todos los campos son también final, lo que hace que los records sean inherentemente inmutables.
Puedes añadir métodos personalizados y sobrescribir los generados automáticamente si es necesario. También puedes añadir validación en un constructor compacto:
record Person(String name, int age) {
public Person {
if (age < 0) throw new IllegalArgumentException("Age cannot be negative");
}
}Los records son perfectos para DTOs, respuestas de API o cualquier escenario en el que necesites un contenedor de datos simple e inmutable sin escribir código repetitivo.
Desafío
FácilVamos a construir un sistema de reserva de boletos de cine que muestre la elegancia de los records de Java. Verás cómo los records eliminan el código repetitivo (boilerplate) mientras te permiten agregar métodos personalizados y validación.
Organizarás tu código en tres archivos:
Movie.java: Crea un record que represente una película con tres componentes:title(String),genre(String) ydurationMinutes(int). Agrega un método personalizado llamadogetFormattedDuration()que devuelva la duración en un formato amigable:[hours]h [minutes]m(por ejemplo, 142 minutos se convierte en2h 22m). Usa la división entera y el módulo para calcular las horas y los minutos restantes.Ticket.java: Crea un record que represente un boleto de cine con tres componentes:movie(Movie),seatNumber(String) yprice(double). Agrega un constructor compacto que valide el precio; si el precio es menor o igual a cero, lanza unaIllegalArgumentExceptioncon el mensaje"Price must be positive". También agrega un método personalizado llamadoprintTicket()que devuelva una cadena multilínea:========== TICKET ========== Movie: [movie title] Genre: [movie genre] Duration: [formatted duration from movie] Seat: [seatNumber] Price: $[price] ============================Main.java: ¡Une tu sistema de reservas! Recibirás cinco entradas: título de la película (String), género (String), duración en minutos (int), número de asiento (String) y precio del boleto (double).Crea un record Movie con las tres primeras entradas, luego crea un record Ticket con la película, el número de asiento y el precio. Imprime el resultado de llamar a
printTicket()en tu boleto.
Recibirás cinco entradas en orden: título de la película, género, duración (minutos), número de asiento y precio.
Observa qué tan limpios son los records en comparación con las clases tradicionales: no es necesario escribir constructores, getters, toString(), equals() o hashCode(). El constructor compacto te permite agregar validación sin repetir las asignaciones de campos, ¡y aún puedes agregar métodos personalizados cuando sea necesario!
Hoja de referencia
Java 16 introdujo los records para eliminar el código repetitivo en las clases simples que transportan datos. Un record es una clase especial diseñada para contener datos inmutables.
Declara un record con la palabra clave record seguida de la lista de componentes:
record Person(String name, int age) { }Esto proporciona automáticamente:
- Campos privados y finales
- Un constructor
- Métodos de acceso (sin el prefijo
get) toString(),equals()yhashCode()
Uso de un record:
Person p = new Person("Alice", 30);
System.out.println(p.name()); // Alice
System.out.println(p.age()); // 30
System.out.println(p); // Person[name=Alice, age=30]Los records son implícitamente final y no se pueden extender. Todos los campos son final, lo que hace que los records sean intrínsecamente inmutables.
Añade validación usando un constructor compacto:
record Person(String name, int age) {
public Person {
if (age < 0) throw new IllegalArgumentException("Age cannot be negative");
}
}Puedes añadir métodos personalizados a los records:
record Person(String name, int age) {
public String getInfo() {
return name + " is " + age + " years old";
}
}Pruébalo tú mismo
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Leer entradas
String title = scanner.nextLine();
String genre = scanner.nextLine();
int durationMinutes = Integer.parseInt(scanner.nextLine());
String seatNumber = scanner.nextLine();
double price = Double.parseDouble(scanner.nextLine());
// TODO: Crear un record Movie con title, genre, y durationMinutes
// TODO: Crear un record Ticket con movie, seatNumber, y price
// TODO: Imprimir el ticket usando el método printTicket()
}
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de OOP
Archivos externosIntroducción a la OOPClases vs. ObjetosLa palabra clave thisMétodosCampos (Atributos)Método constructorSobrecarga de constructoresResumen - Calculadora simple4Herencia
Herencia básica (extends)La palabra clave superSobrescritura de métodos (@Override)Encadenamiento de constructoresLa clase ObjectHerencia simple y multinivelPor qué no hay herencia múltiple de clasesResumen - Jerarquía de empleados7Métodos especiales y la clase Object
Método toString()equals() y hashCode()Método clone()compareTo() y ComparableInterfaz ComparatorResumen - Ordenamiento personalizado2Modificadores de acceso y encapsulamiento
Visión general de los niveles de accesoMétodos Getter y SetterOcultamiento de informaciónLa palabra clave finalResumen - Gestor de cuentas bancarias5Polimorfismo
Fundamentos de sobrecarga de métodosSobrescritura de métodos (tiempo de ejecución)Upcasting y DowncastingEl operador instanceofClases y métodos abstractosResumen - Calculadora de figuras8Conceptos avanzados de POO
Composición vs. HerenciaAgregación vs. ComposiciónClases internas, anidadas y anónimasEnums y métodos de EnumRecords (Java 16+)Clases selladas (Java 17+)11Patrones de diseño - Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón BuilderPatrón ObserverPatrón Strategy3Propiedades de clase y miembros estáticos
Variables de instancia vs. estáticasMétodos estáticosBloques estáticosConstantes (static final)Repaso - Contador y utilidad6Interfaces y Clases Abstractas
Introducción a las InterfacesImplementación de InterfacesImplementación de Múltiples InterfacesMétodos Default y Static en InterfacesClases Abstractas vs InterfacesInterfaces FuncionalesResumen - Sistema de Pagos9Genéricos
Introducción a los genéricosClases genéricasMétodos genéricosParámetros de tipo delimitadosComodines (?, extends, super)Resumen - Contenedor genérico12Patrones de diseño Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositePatrón Iterator