CodeGym /Cursos /JAVA 25 SELF /Diferencias entre record y class, y limitaciones de recor...

Diferencias entre record y class, y limitaciones de record

JAVA 25 SELF
Nivel 22 , Lección 4
Disponible

1. Comparación entre record y class: ¿cuáles son las principales diferencias?

En Java tenemos dos formas principales de describir nuestros tipos de datos: mediante clases normales (class) y mediante clases record (record). A primera vista, ambas opciones permiten almacenar y procesar datos. Pero si profundizamos un poco, ¡hay más diferencias de las que parece!

Tabla de diferencias: class vs record

Característica Clase normal (class) Clase record (record)
Mutabilidad Cualquiera: los campos pueden ser final o no Inmutable: todos los campos son final
Herencia Puede heredar (extends), no es final por defecto Siempre final, no puede ser superclase
Campos Cualquiera: estáticos, no estáticos, final o no final, de cualquier tipo Solo componentes del record (private final), más campos estáticos
Getters/setters Los escribimos nosotros (o los generamos con Lombok) Se crean getters automáticamente (el nombre del campo es el nombre del método), no hay setters
equals/hashCode/toString Normalmente se escriben a mano/se generan (equals, hashCode, toString) Se generan automáticamente a partir de todos los componentes
Constructores Cualquiera, tantos como quieras Uno principal (para todos los componentes); se puede añadir un constructor compacto
Interfaces Se pueden implementar Se pueden implementar
Métodos adicionales Cualquiera Se pueden añadir, pero solo métodos (no campos)
Uso en colecciones Se puede, pero hay que implementar correctamente equals/hashCode Ideal para claves/valores; todo ya está implementado

Ejemplo ilustrativo

Clase normal:


public class Person {
    private final String name;
    private final int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() { return name; }
    public int getAge() { return age; }

    @Override
    public boolean equals(Object o) { /* ... */ }
    @Override
    public int hashCode() { /* ... */ }
    @Override
    public String toString() { /* ... */ }
}

Clase record:


public record Person(String name, int age) { }

¡Listo! Una sola línea de código — y obtenemos lo mismo (e incluso mejor). Y sin riesgo de olvidar implementar algo importante.

2. Limitaciones de las clases record

Las clases record no son solo «sintaxis corta», sino un concepto aparte con reglas estrictas. Veámoslas con más detalle.

Un record es siempre final

Una clase record por definición es siempre final. Esto significa que no puedes crear una subclase de un record:


public record Point(int x, int y) { }

// public class ColoredPoint extends Point { } // Error de compilación!

Si necesitas ampliar el comportamiento, usa clases normales o composición (incrusta un record dentro de una clase).

Un record no puede ser superclase

Una clase record no puede ser la clase padre de otras clases; siempre es final. Tiene sentido: si eso fuera posible, alguien podría añadir un campo mutable y todo el concepto de «datos inmutables» se vendría abajo.

Solo campos final (componentes)

Todos los componentes de un record se declaran en el encabezado y por defecto son private final. No puedes añadir campos no estáticos en el cuerpo de un record:


public record User(String login, String email) {
    // int counter; // Error: no se pueden añadir campos no estáticos
    static int totalUsers = 0; // Permitido: es un campo estático
}

No hay setters

Una clase record no puede tener setters para sus componentes. Cualquier intento de añadir un método como setX(int x) no tendrá sentido: no podrás cambiar el valor del campo después de crear el objeto.


public record Point(int x, int y) {
    // public void setX(int x) { this.x = x; } // Error: no se puede modificar un campo final
}

No hay constructor vacío

Una clase record siempre tiene un único constructor principal que recibe valores para todos los componentes. No se puede crear un record sin proporcionar todos los datos:


Point p = new Point(1, 2);  // OK
// Point p = new Point();   // Error: no existe un constructor sin parámetros

No hay inicializadores no estáticos

Una clase record no puede contener inicializadores no estáticos (los que se escriben entre llaves fuera de los métodos):


public record User(String login) {
    // { /* ... */ } // Error: los inicializadores no estáticos están prohibidos
}

Restricciones de herencia

Una clase record no puede heredar explícitamente de otra clase (excepto de java.lang.Record, que está oculta como clase base de todos los record). ¡Pero sí puede implementar interfaces!


public interface Printable {
    void print();
}

public record Book(String title) implements Printable {
    @Override
    public void print() {
        System.out.println("Imprimiendo el libro: " + title);
    }
}

No es adecuado para lógica de negocio compleja

El record trata de datos, no de comportamiento. Si tu objeto tiene lógica compleja, estado mutable, «ciclo de vida» o muchas dependencias, el record no te ayudará. Es mejor usar una clase normal.

3. ¿Cuándo conviene usar clases record?

  • DTO (Data Transfer Object): para transferir datos inmutables entre capas de la aplicación, servicios, microservicios o controladores REST (por ejemplo, en respuestas JSON).
  • Value Object: objetos que se definen únicamente por sus valores.
  • Claves y valores en colecciones: cuando es importante una implementación correcta de equals y hashCode (por ejemplo, para usar en HashMap o Set).
  • Resultados de cálculos: cuando hay que devolver varios valores de un método (por ejemplo, record Pair<T, U>(T first, U second)).

Ejemplo: DTO para un controlador REST


public record UserDto(String login, String email) { }

Ahora puedes devolver con total seguridad un objeto de este tipo desde el controlador, sin temor a que alguien modifique sus campos.

Ejemplo: clave para HashMap


public record Point(int x, int y) { }

Map<Point, String> pointNames = new HashMap<>();
pointNames.put(new Point(1, 2), "A");
pointNames.put(new Point(3, 4), "B");

// ¡Todo funciona correctamente!: equals y hashCode ya están implementados

4. Cuándo NO deberías usar clases record

  • Estado mutable: si al menos un campo debe cambiar tras crear el objeto.
  • Lógica compleja: si el objeto tiene un comportamiento complejo, muchos métodos u objetos anidados con estado mutable.
  • Herencia: si se requiere una jerarquía de clases, clases base abstractas o sobreescritura de métodos.
  • Entidades de negocio: por ejemplo, objetos que viven en la base de datos y tienen un identificador único.

Ejemplo: cuando necesitas una clase normal


public class Account {
    private String id;
    private int balance;

    public Account(String id, int balance) {
        this.id = id;
        this.balance = balance;
    }

    public void deposit(int amount) { balance += amount; }
    public void withdraw(int amount) { balance -= amount; }
    // getters, setters, equals, hashCode, toString...
}

Aquí se ve claramente que el estado del objeto cambia — el record no es adecuado.

5. Ejemplos prácticos: elegir entre record y class

Ejemplo 1: record — elección ideal


public record Rectangle(int width, int height) {
    public int area() {
        return width * height;
    }
}
  • El rectángulo se define solo por su anchura y altura.
  • No es necesario cambiar estos valores tras la creación.
  • Puedes añadir un método útil area().
  • El resto lo hace Java por ti.

Ejemplo 2: class — mejor opción


public class MutableRectangle {
    private int width;
    private int height;

    public MutableRectangle(int width, int height) {
        this.width = width;
        this.height = height;
    }

    public void setWidth(int width) { this.width = width; }
    public void setHeight(int height) { this.height = height; }

    public int area() { return width * height; }
}

¿Necesitas cambiar las dimensiones del rectángulo después de crearlo? Usa una clase normal.

6. Errores típicos al trabajar con clases record

Error n.º 1: intento de añadir un campo no estático.
Una clase record no permite declarar campos no estáticos fuera de la lista de componentes. Si lo intentas, el compilador dará un error. Por ejemplo:


public record City(String name) {
    // int population; // ¡Error!
}

Error n.º 2: querer añadir un setter.
Un record no admite setters para sus componentes. Cualquier intento de cambiar el valor de un campo después de crear el objeto es un error de compilación.

Error n.º 3: intentar heredar de un record o que un record herede.
Un record siempre es final. No se puede heredar de un record y un record no puede heredar de otra clase (salvo del oculto java.lang.Record).

Error n.º 4: usar record para objetos mutables.
Si planeas cambiar el estado del objeto después de crearlo, ¡record no es para ti! Usa una clase normal.

Error n.º 5: olvidar las restricciones del constructor.
Una clase record debe tener obligatoriamente un constructor que reciba valores para todos los componentes. ¡No hay constructor sin parámetros!

1
Cuestionario/control
Clases record, nivel 22, lección 4
No disponible
Clases record
Clases record
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION