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é ocurre 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 mediante las palabras clave sealed y permits:
sealed abstract 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 provocará un error de compilación.
Cada subclase permitida debe usar uno de tres modificadores: final (sin extensiones posteriores), sealed (continúa la cadena de restricciones) o non-sealed (permite extensiones 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 sealed classes son especialmente potentes con la coincidencia de patrones en las 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ácilConstruyamos un sistema de notificaciones que demuestre las clases selladas restringiendo qué tipos de notificaciones pueden existir en tu aplicación. Crearás una jerarquía en la que solo se permiten tipos de notificación específicos, y cada tipo gestionará la entrega de una manera diferente.
Organizarás tu código en cuatro archivos:
Notification.java: Crea una clase abstracta sellada 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 Notification. Un 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 y llamar al constructor principal consuper(). El métododeliver()debe devolver:Sending email to [recipient]: [message]SMSNotification.java: Crea una clasenon-sealedque extienda Notification. Esto permite que otras clases la extiendan si es necesario en el futuro. Un 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]: [message]PushNotification.java: Crea una clasefinalque extienda Notification. Un 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]: [message]Main.java: ¡Reúne 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 utilizando el mismo mensaje, pero su información de contacto correspondiente. Almacena las tres en un array de tipo
Notification[]para demostrar el polimorfismo con clases selladas. Después, recorre el array e imprime el resultado de llamar adeliver()en cada notificación.
Recibirás cuatro entradas en este 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 proporciona un control completo sobre tu jerarquía de tipos y, al mismo tiempo, permite el polimorfismo!
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 uno de cada tipo de notificación usando el mismo mensaje
// - EmailNotification con message y email
// - SMSNotification con message y phoneNumber
// - PushNotification con message y deviceId
// TODO: Almacena las tres notificaciones en un array Notification[]
// Esto demuestra el polimorfismo con clases selladas
// TODO: Itera a través del array 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 IteratorPractica por tu cuenta: Compilador de Java online