Ir al contenido principal

Java: sealed class & interfaces

 


El concepto de "clases e interfaces selladas" surge desde la versión Java 15 hasta la 17. Pero, ¿Qué son y para qué sirven las clases e interfaces sealed?

Estas construcciones permiten un control más estricto sobre la herencia (extends) y la implementación (implements), lo que mejora la seguridad, el diseño del código y la claridad en la intención del desarrollador.

  1. Clases sealed: Una clase sellada es una clase que restringe qué otras clases pueden extenderla. Esto se logra especificando explícitamente las subclases permitidas mediante la palabra clave permits
  2.  Interfaces sealed: Similar a las clases selladas, una interfaz sellada restringe qué clases o interfaces pueden implementarla o extenderla, también usando permits.

Ejemplos de clases sealed

public sealed class Vehiculo permits Coche, Moto {
    // Código de la clase
}

final class Coche extends Vehiculo {
    // Código de la clase
}

final class Moto extends Vehiculo {
    // Código de la clase
}

// Esto generaría un error de compilación
class Camion extends Vehiculo {
    // No permitido, ya que no está en la lista de permits
}

Ejemplos de interfaces sealed

public sealed interface Animal permits Perro, Gato {
    void hacerSonido();
}

final class Perro implements Animal {
    public void hacerSonido() {
        System.out.println("Guau");
    }
}

final class Gato implements Animal {
    public void hacerSonido() {
        System.out.println("Miau");
    }
}

// Esto generaría un error de compilación
class Elefante implements Animal {
    // No permitido, ya que no está en la lista de permits
}

Las clases e interfaces selladas tienen varios usos y beneficios:

  •  Control estricto de la jerarquía de herencia: Permiten al desarrollador definir explícitamente qué clases pueden heredar de una clase o implementar una interfaz, evitando extensiones no deseadas. Esto es útil para diseñar APIs o bibliotecas donde se quiere limitar cómo los usuarios pueden extender el comportamiento. 
  •  Mejora de la seguridad y robustez: Al restringir la herencia, se reduce el riesgo de que clases externas rompan invariantes o comportamientos esperados de la clase base. Por ejemplo, en sistemas críticos, esto puede prevenir errores o vulnerabilidades causados por extensiones no controladas. 
  • Facilitan el uso de pattern matching: Las clases selladas son especialmente útiles con el switch de pattern matching. Como el compilador sabe exactamente qué subclases existen, puede garantizar que todos los casos posibles estén cubiertos.
  • Claridad en el diseño: Las clases e interfaces selladas hacen explícita la intención del diseño, mejorando la legibilidad y el mantenimiento del código. Los desarrolladores que usen tu código sabrán exactamente qué extensiones están permitidas, lo que reduce la ambigüedad.

Imaginemos que diseñamos un sistema de figuras geométricas:

public sealed abstract class Figura permits Circulo, Rectangulo {
    public abstract double calcularArea();
}

final class Circulo extends Figura {
    private double radio;
    public Circulo(double radio) { this.radio = radio; }
    public double calcularArea() { return Math.PI * radio * radio; }
}

final class Rectangulo extends Figura {
    private double largo, ancho;
    public Rectangulo(double largo, double ancho) { this.largo = largo; this.ancho = ancho; }
    public double calcularArea() { return largo * ancho; }
}

En este caso, solo Circulo y Rectangulo pueden extender Figura, lo que asegura que no se añadan figuras no deseadas al sistema.

Ejemplo. Crearemos una sealed interface con 4 clases record y la implementaremos, simulando éxitos y fallos de ejecución:

  • Success si la operación tiene éxito. 
  • Failure si ocurre un error. 
  • Timeout si se excede el tiempo permitido. 
  • Interrupted si la operación es interrumpida.
sealed interface AsyncReturn<V> {
    record Success<V>(V result) implements AsyncReturn<V> {}
    record Failure<V>(Throwable cause) implements AsyncReturn<V> {}
    record Timeout<V>() implements AsyncReturn<V> {}
    record Interrupted<V>() implements AsyncReturn<V> {}
}

Esta interface no será public, ya que se encontrará en la clase principal que la implementará:

TestSealedClassInterfaces.java

import java.util.concurrent.Future;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

// Definición de la interfaz sealed (como la proporcionaste)
sealed interface AsyncReturn<V> {
    record Success<V>(V result) implements AsyncReturn<V> {}
    record Failure<V>(Throwable cause) implements AsyncReturn<V> {}
    record Timeout<V>() implements AsyncReturn<V> {}
    record Interrupted<V>() implements AsyncReturn<V> {}
}

public class TestSealedClassInterfaces {
    // Método que simula una operación asíncrona y devuelve un AsyncReturn
    public static <V> AsyncReturn<V> ejecutarOperacion(CompletableFuture<V> future, long timeoutMillis) {
        try {
            // Intentamos obtener el resultado con un tiempo límite
            V resultado = future.get(timeoutMillis, TimeUnit.MILLISECONDS);
            return new AsyncReturn.Success<>(resultado);
        } catch (java.util.concurrent.TimeoutException e) {
            return new AsyncReturn.Timeout<>();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); // Restaurar el estado de interrupción
            return new AsyncReturn.Interrupted<>();
        } catch (Exception e) {
            return new AsyncReturn.Failure<>(e);
        }
    }

    // Método para manejar el resultado de AsyncReturn
    public static <V> void manejarResultado(AsyncReturn<V> resultado) {
        switch (resultado) {
            case AsyncReturn.Success<V>(var valor) -> {
                System.out.println("Operación exitosa. Resultado: " + valor);
            }
            case AsyncReturn.Failure<V>(var causa) -> {
                System.out.println("Error en la operación: " + causa.getMessage());
            }
            case AsyncReturn.Timeout<V> __ -> {
                System.out.println("La operación excedió el tiempo permitido.");
            }
            case AsyncReturn.Interrupted<V> __ -> {
                System.out.println("La operación fue interrumpida.");
            }
        }
    }

    public static void main(String[] args) {
        // Ejemplo 1: Operación exitosa
        CompletableFuture<String> futureExitoso = CompletableFuture.supplyAsync(() -> "¡Éxito!");
        AsyncReturn<String> resultadoExitoso = ejecutarOperacion(futureExitoso, 1000);
        manejarResultado(resultadoExitoso);

        // Ejemplo 2: Operación con error
        CompletableFuture<String> futureFallido = CompletableFuture.supplyAsync(() -> {
            throw new RuntimeException("Algo salió mal");
        });
        AsyncReturn<String> resultadoFallido = ejecutarOperacion(futureFallido, 1000);
        manejarResultado(resultadoFallido);

        // Ejemplo 3: Operación con timeout
        CompletableFuture<String> futureLento = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(2000); // Simula una operación lenta
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            return "Demasiado tarde";
        });
        AsyncReturn<String> resultadoTimeout = ejecutarOperacion(futureLento, 500);
        manejarResultado(resultadoTimeout);
    }
}

Salida:

$ java TestSealedClassInterfaces
Operación exitosa. Resultado: ¡Éxito!
Error en la operación: java.lang.RuntimeException: Algo salió mal
La operación excedió el tiempo permitido.

Las clases e interfaces sealed en Java son herramientas poderosas para diseñar sistemas más seguros, claros y mantenibles. Son especialmente útiles en APIs, frameworks y sistemas donde la herencia debe ser estrictamente controlada, y se integran perfectamente con características modernas como el pattern matching.

Enlaces:

https://www.arquitecturajava.com/java-sealed-classes-y-jdk-17/
https://javarevisited.blogspot.com/2022/02/how-to-use-sealed-classes-and-interfaces.html#axzz90QmSSReh
https://www.baeldung.com/java-sealed-classes-interfaces

Comentarios

Entradas populares de este blog

Odin language: el lenguaje de programación orientado a datos para un desarrollo de software sensato

En esta serie sobre lenguajes de programación hablamos de Odin , un lenguaje orientado a datos. Un nuevo lenguaje de programación que nos podrá recordar a lenguajes como C++, Python, Golang o hasta Rust. Instalación (Windows): 1 2 3 4 $ git clone https://github.com/odin-lang/Odin $ cd Odin $ git pull $ build.bat release Debemos asegurarnos de crear la variable de entorno: Nombre: ODIN_HOME Valor: C:\Users\HP\Documents\pruebasOdin\Odin Ejemplo 1. Como primer programa tendremos al clásico "Hola, mundo". holamundo.odin package holamundo import " core:fmt " main : : proc ( ) { fmt . println ( " ¡Hola, mundo desde Odin! " ) } Construimos: $ odin build holamundo . odin - file Ejecutamos: $ holamundo .exe Recordar que si usamos Visual Studio Code podemos instalar el plugin de Odin Language: https://marketplace.visualstudio.com/items?itemName=DanielGavin.ols Ejemplo 2. Programa que muestre las variables que pod...

Programación Windows Batch (CMD) parte 3

Crear ciclos para efectuar operaciones tediosas nos ahorrará tiempo para realizar otras tareas. En está ocasión veremos ciclos con FOR . ¿Cuál es la síntaxis de bucle FOR en Windows Batch? Si está dentro de un archivo *.bat : FOR %%variable IN (seq) DO operaciones Si lo ejecutamos en una terminal: FOR %variable IN (seq) DO operaciones Ejemplo 1 . Recorrer una secuencia de números del 0 al 5: recorrer.bat @ echo off FOR %%i in ( 0 1 2 3 4 5 ) DO echo Hola no. %%i pause Nos imprimirá en pantalla: Hola no. 0 Hola no. 1 Hola no. 2 Hola no. 3 Hola no. 4 Hola no. 5 ¿Puedo usar contadores? Si, se pueden usar. Ejemplo 2 . Uso de contadores: contador.bat @ echo off set numeros = 1 2 3 4 5 6 7 8 9 10 set cont = 0 for %%a in ( %numeros% ) do ( echo Hola no. %%a :: Contador set /a cont+ = 1 ) echo Total: %cont% Este código nos imprimirá, además de los mensajes Hola no. 0 ..., el total de valores conta...

z/OS, ¿Qué es? y ¿Cuáles son sus herramientas?

  En este post veremos las herramientas de z/OS de IBM. z/OS es un sistema operativo de mainframe desarrollado por IBM.  Es parte de la serie IBM z Systems (anteriormente conocida como System z), que se utiliza en los sistemas mainframe de IBM. z/OS es uno de los sistemas operativos más utilizados en el mundo empresarial para ejecutar aplicaciones críticas y procesar grandes volúmenes de datos. Características clave de z/OS: Escalabilidad : z/OS es conocido por su capacidad para manejar grandes cargas de trabajo y escalabilidad vertical, lo que permite agregar recursos de hardware para satisfacer las demandas de procesamiento. Confiabilidad y disponibilidad : z/OS está diseñado para ofrecer alta disponibilidad y confiabilidad. Incorpora características como redundancia y recuperación ante fallas para garantizar la continuidad de las operaciones. Seguridad : Proporciona una amplia gama de funciones de seguridad, como autenticación, autorización y auditoría, para proteger los...