Skip to main content

Clases selladas en Java

⏱ Dedicación recomendada: 0 minutos
Esto considera el contenido visible y relevante, e ignora texto colapsado o marcado como opcional.


r8vnhill/java-dibs

En Java, las clases selladas (introducidas en Java 15 como una característica preliminar y oficializadas en Java 17) se utilizan de manera similar a Kotlin, permitiendo controlar qué subclases pueden extender una clase o implementar una interfaz. Aunque existen similitudes con Kotlin, también hay diferencias importantes, particularmente en cómo Java maneja la exhaustividad en los patrones switch.

Ejemplo: Sistema de Pagos en Java

public sealed interface Payment permits CreditCard, Cash, Unpaid {}

public record CreditCard(String number) implements Payment { }

public record Cash(int amount) implements Payment { }

public final class Unpaid implements Payment {}

En este ejemplo, la interfaz Payment es sellada y solo permite tres implementaciones: CreditCard, Cash y Unpaid. Esto es similar a cómo Kotlin restringe las subclases de una clase sellada.

Ausencia de "data objects"

Es importante resaltar que los records en Java necesitan recibir parámetros en su constructor, lo que impide que existan equivalentes directos a los data objects de Kotlin, debido a esto, se utiliza una clase cerrada (final) con un constructor vacío (que se genera automáticamente) para simular el comportamiento de un data object.

Manejo con switch en Java

En versiones recientes de Java, se puede utilizar un switch exhaustivo, pero a menudo es necesario incluir un caso default para manejar la exhaustividad, esto introduce un riesgo de errores si se agregan nuevas subclases en el futuro así que debe manejarse con precaución.

public static void handlePayment(Payment payment) {
switch (payment) {
case CreditCard cc -> System.out.println("Credit card: " + cc.getNumber());
case Cash cash -> System.out.println("Cash amount: " + cash.getAmount());
case Unpaid unpaid -> System.out.println("Payment is unpaid");
default -> throw new IllegalStateException("Unexpected payment type: " + payment);
}
}

Diferencias Clave con Kotlin

  1. Palabras clave:
  • En Java, la restricción de subclases se especifica con permits en la declaración de la clase sellada. En Kotlin, las subclases deben estar en el mismo paquete y módulo sin una declaración explícita.
  1. Exhaustividad:
  • En Kotlin, un when sobre una clase sellada debe ser exhaustivo; si no se cubren todos los casos, el código no compilará. En Java, aunque el switch puede ser exhaustivo, aún es común usar un caso default para manejar casos no contemplados o no permitidos.
  1. Manejo de objetos sellados:
  • Kotlin tiene una sintaxis más expresiva para el manejo de clases selladas con el uso de data objects y data classes, mientras que en Java, todas las subclases deben ser clases concretas que extienden o implementan la clase o interfaz sellada.

Reflexión en Java

En Java, listar todas las subclases de una clase sellada no es una tarea sencilla como en Kotlin con sealedSubclasses. No existe un equivalente directo, por lo que es necesario utilizar técnicas de reflexión avanzada o depender de bibliotecas externas que escanean el classpath para obtener las subclases de una clase.

Uso de Reflections en Java

Una manera de obtener todas las subclases de una clase sellada en Java es utilizar una biblioteca de reflexión como Reflections. Esta biblioteca permite buscar clases que implementan o extienden otras clases en el classpath.

Paso 1: Agregar la dependencia de Reflections

Agrega la dependencia de Reflections en tu archivo build.gradle.kts.

implementation("org.reflections:reflections:0.10.2")

Paso 2: Listar las subclases de una clase sellada

Ahora, puedes usar Reflections para encontrar las subclases de una clase sellada. Aquí tienes un ejemplo que lista todas las subclases de Payment, una interfaz sellada:

Reflections reflections = new Reflections("cl.ravenhill");
Set<Class<? extends Payment>> subtypes = reflections.getSubTypesOf(Payment.class);
subtypes.forEach(subtype -> System.out.println("Subclass: " + subtype.getName()));

En este ejemplo, la clase Reflections escanea el paquete "cl.ravenhill" en el classpath y encuentra todas las clases que extienden o implementan Payment.

Limitaciones

  • Reflections solo puede encontrar subclases que están en el classpath al momento de la ejecución. Si las subclases están distribuidas en otros módulos o paquetes no incluidos en el classpath, no serán detectadas.
  • Es necesario configurar el paquete correcto en la clase Reflections, lo que puede ser un inconveniente si tienes una jerarquía de paquetes compleja.

Comparación con Kotlin

En Kotlin, el uso de la reflexión para obtener las subclases de una clase sellada es mucho más sencillo gracias a la función sealedSubclasses. No es necesario usar bibliotecas externas ni configurar el escaneo de paquetes manualmente. Solo necesitas invocar sealedSubclasses directamente:

fun listOrderStates() = DeliveryState::class.sealedSubclasses

Esto es posible debido a que Kotlin mantiene internamente la lista de subclases en el tiempo de compilación, lo que hace que la operación sea más eficiente y fácil de usar. En contraste, Java requiere bibliotecas adicionales y técnicas más avanzadas para lograr el mismo resultado.

Bibliografías Recomendadas

  • 📚 "".
  • 🌐 "." [En línea]. Disponible en:
  • 📰 "."