Análisis de las Limitaciones de Combinación
Java introdujo las Clases Selladas (Sealed Classes) y las Clases de Registro (Records) como características que, aunque complementarias, presentan incompatibilidades fundamentales en su combinación directa. Las clases selladas definen una jerarquía de herencia controlada mediante la cláusula permits, que lista explícitamente las subclases permitidas. Sin embargo, las clases de registro están diseñadas como portadores de datos inmutables y poseenn una semántica implícita de final, lo que prohíbe cualquier herencia. Esta colisión de principios impide que un registro sea listado como subclase directa de una clase sellada en la mayoría de los contextos de diseño.
Restricciones Semánticas y de Implementación
La Naturaleza Final de los Registros
Una clase de registro (record) es, por definición, un tipo final. Su propósito principal es encapsular datos de manera concisa e inmutable, generando automáticamente métodos constructores, de acceso (accessors), equals(), hashCode() y toString(). Esta finalidad choca directamente con el requisito de la extensibilidad controlaad que subyace al modelo de herencia de las clases selladas. Un registro no puede ser una superclase, ni tampoco puede ser declarado como non-sealed.
Validación del Permite en Tiempo de Compilación
El compilador de Java realiza comprobaciones estrictas sobre los tipos listados en permits. Dichos tipos deben ser accesibles, estar en el mismo módulo o paquete, y deben extender directamente a la clase sellada padre. Crucially, un tipo listado debe poder ser extendido a su vez, a menos que sea final. Si bien un registro es final, la semántica de las clases selladas a menudo espera tipos que puedan formar parte de una jerarquía más amplia, lo que genera conflicto conceptual.
// Ejemplo de código inválido
public sealed interface SerVivo permits Animal, Planta {}
// Error: Un record no puede implementar una interfaz sellada
// porque su naturaleza 'final' restringe la extensibilidad.
public record Animal(String nombre) implements SerVivo {}
// Alternativa válida: Usar una clase final normal.
public final class Planta implements SerVivo {
private final String tipo;
public Planta(String tipo) { this.tipo = tipo; }
}
Patrones de Diseño Alternativos
Para reconciliar ambos conceptos, se requiere indirección. Un enfoque común es no listar el registro directamente en permits, sino envolverlo dentro de una clase final o no sellada que sí cumpla con las restricciones de la jerarquía sellada.
public sealed interface Expresion permite Operacion, Constante {}
// Una clase final actúa como adaptador para el registro.
public final class Constante implements Expresion {
private final Valor valor; // 'Valor' podría ser un record
public Constante(double v) {
this.valor = new Valor(v);
}
// ... delegación y métodos de Expresión
}
public record Valor(double cantidad) {} // Un record puro para datos.
public final class Operacion implements Expresion {
private final Expresion izquierda;
private final Expresion derecha;
// ...
}
Otro patrón es utilizar registros como componentes internos de una clase sellada, sin que participen en la jerarquía de herencia misma.
public sealed abstract class Figura {
public abstract double area();
// Los registros viven como miembros internos.
public record Circulo(double radio) {
// No implementa Figura, solo contiene datos.
}
public record Rectangulo(double ancho, double alto) {}
}
public final class FiguraCircular extends Figura {
private final Figura.Circulo datos;
public FiguraCircular(double radio) {
this.datos = new Figura.Circulo(radio);
}
@Override
public double area() {
return Math.PI * datos.radio() * datos.radio();
}
}
Implicaciones en el Diseño de Software
Esta separación fomenta diseños más explícitos donde el comportamiento (las clases de la jerarquía sellada) se mantiene separado de la representación de datos (los registros). Aunque requiere más código boilerplate, resulta en una mayor claridad arquitectónica. El compilador puede realizar comprobaciones de exhaustividad en sentencias switch con expresiones y patrones, garantizando que todos los tipos de la jerarquía sellada estén cubiertos.
| Patrón | Ventaja Principal | Desventaja |
|---|---|---|
| Envoltorio (Wrapper) Final | Mantiene el registro puro y la jerarquía sellada clara. | Añade una capa de indirección. |
| Componente Interno | Co-localiza la definición de datos con su contexto. | La clase sellada padre se vuelve más compleja. |
| Uso de Clases de Datos Normales | Compatibilidad total con el sistema de tipos sellados. | Pierde las ventajas sintácticas de los registros. |
La evolución futura de Java podría introducir modulaciones en estas reglas, como permitir registros sellados o registros en roles específicos dentro de una jerarquía, pero actualmente, el diseño consciente que separa la herencia de la representación inmutable de datos es la vía recomendada.