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.
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
- 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.
- 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 elswitch
puede ser exhaustivo, aún es común usar un casodefault
para manejar casos no contemplados o no permitidos.
- Manejo de objetos sellados:
- Kotlin tiene una sintaxis más expresiva para el manejo de clases selladas con el uso de
data objects
ydata 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:
- 📰 "."