Clases selladas (Java 17+)
Parte de la sección Programación Orientada a Objetos del Journey de Java de Coddy — lección 52 de 87.
En Java tradicional, cuando creas una clase, cualquier otra clase puede extenderla (a menos que la marques como final). ¿Pero qué pasa si quieres permitir la herencia, pero no de cualquiera? Java 17 introdujo las clases selladas para darte un control preciso sobre qué clases pueden extender la tuya.
Una clase sellada declara explícitamente sus subclases permitidas utilizando las palabras clave sealed y permits:
sealed class Shape permits Circle, Rectangle, Triangle {
abstract double area();
}
final class Circle extends Shape {
double radius;
double area() { return Math.PI * radius * radius; }
}
final class Rectangle extends Shape {
double width, height;
double area() { return width * height; }
}
final class Triangle extends Shape {
double base, height;
double area() { return 0.5 * base * height; }
}Solo Circle, Rectangle, y Triangle pueden extender Shape. Cualquier otra clase que intente extenderla causará un error de compilación.
Cada subclase permitida debe usar uno de tres modificadores: final (sin extensión adicional), sealed (continúa la cadena de restricción) o non-sealed (se abre para una extensión sin restricciones):
sealed class Vehicle permits Car, Truck { }
final class Car extends Vehicle { } // No se puede extender
non-sealed class Truck extends Vehicle { } // Cualquiera puede extender TruckLas clases selladas son particularmente potentes con la coincidencia de patrones (pattern matching) en expresiones switch, ya que el compilador conoce todos los subtipos posibles y puede verificar la exhaustividad. Son ideales al modelar un conjunto fijo de tipos en un dominio, como métodos de pago, tipos de respuesta o formas geométricas.
Desafío
Fácil¡Vamos a construir un sistema de notificaciones que demuestre las clases selladas (sealed classes) restringiendo qué tipos de notificaciones pueden existir en tu aplicación! Crearás una jerarquía donde solo se permiten tipos de notificación específicos, y cada tipo maneja la entrega de manera diferente.
Organizarás tu código en cuatro archivos:
Notification.java: Crea una clase abstracta sellada (sealed abstract class) que sirva como base para todas las notificaciones. Debe permitir exactamente tres subclases:EmailNotification,SMSNotificationyPushNotification. Tu clase sellada necesita un campo protegidomessage(String), un constructor para inicializarlo, un getter para el mensaje y un método abstractodeliver()que devuelva un String describiendo cómo se envía la notificación.EmailNotification.java: Crea una clasefinalque extienda de Notification. Una EmailNotification tiene un campo privado adicionalrecipient(String) para la dirección de correo electrónico. Su constructor debe recibir tanto el mensaje como el destinatario, llamando al constructor padre consuper(). El métododeliver()debe devolver:"Sending email to " + recipient + ": " + messageSMSNotification.java: Crea una clasenon-sealedque extienda de Notification; esto permite que otras clases la extiendan si es necesario en el futuro. Una SMSNotification tiene un campo privado adicionalphoneNumber(String). Su constructor recibe el mensaje y el número de teléfono. El métododeliver()debe devolver:"Sending SMS to " + phoneNumber + ": " + messagePushNotification.java: Crea una clasefinalque extienda de Notification. Una PushNotification tiene un campo privado adicionaldeviceId(String). Su constructor recibe el mensaje y el ID del dispositivo. El métododeliver()debe devolver:"Sending push to device " + deviceId + ": " + messageMain.java: ¡Une todo tu sistema de notificaciones! Recibirás cuatro entradas: un mensaje (String), una dirección de correo electrónico (String), un número de teléfono (String) y un ID de dispositivo (String).Crea una notificación de cada tipo usando el mismo mensaje pero con su respectiva información de contacto. Almacena las tres en un arreglo de tipo
Notification[]para demostrar el polimorfismo con clases selladas. Luego, itera a través del arreglo e imprime el resultado de llamar adeliver()en cada notificación.
Recibirás cuatro entradas en orden: mensaje, dirección de correo electrónico, número de teléfono e ID de dispositivo.
Observa cómo la clase sellada restringe la jerarquía: solo las tres clases permitidas pueden extender Notification. Cada subclase debe declararse como final, sealed o non-sealed. ¡Esto te da un control total sobre tu jerarquía de tipos mientras sigues permitiendo el polimorfismo!
Hoja de referencia
Una clase sellada (sealed class) restringe qué clases pueden extenderla utilizando las palabras clave sealed y permits:
sealed class Shape permits Circle, Rectangle, Triangle {
abstract double area();
}Solo las clases enumeradas en permits pueden extender la clase sellada. Cualquier otra clase que intente extenderla provocará un error de compilación.
Cada subclase permitida debe utilizar uno de tres modificadores:
final- evita extensiones adicionalessealed- continúa la cadena de restricción con sus propias subclases permitidasnon-sealed- abre la jerarquía para una extensión sin restricciones
sealed class Vehicle permits Car, Truck { }
final class Car extends Vehicle { } // No se puede extender
non-sealed class Truck extends Vehicle { } // Cualquiera puede extender TruckLas clases selladas son útiles para modelar un conjunto fijo de tipos en un dominio, como métodos de pago, tipos de respuesta o formas geométricas. Funcionan bien con la coincidencia de patrones (pattern matching) en expresiones switch, ya que el compilador conoce todos los subtipos posibles.
Pruébalo tú mismo
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Lee las cuatro entradas
String message = scanner.nextLine();
String email = scanner.nextLine();
String phoneNumber = scanner.nextLine();
String deviceId = scanner.nextLine();
// TODO: Crea una notificación de cada tipo usando el mismo message
// - EmailNotification con message y email
// - SMSNotification con message y phoneNumber
// - PushNotification con message y deviceId
// TODO: Almacena las tres notificaciones en un arreglo Notification[]
// Esto demuestra el polimorfismo con sealed classes
// TODO: Itera a través del arreglo e imprime el resultado de deliver()
// para cada notificación
}
}
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