Tipos de módulos en Java
Módulos con nombre, automáticos y sin nombre en Java, y cómo interactúan durante la compilación y la ejecución.
El Java Platform Module System (JPMS) reconoce tres tipos de módulo. Solo uno es el "real" que tú creas; los otros dos existen para que los millones de JARs anteriores a Java 9 sigan funcionando. Entender en qué tipo se convierte un JAR determinado — y que depende por completo de dónde lo coloca — es la clave para migrar sin problemas. Esta página define los tres tipos, muestra las reglas de acceso entre ellos y demuestra las categorías con un programa ejecutable.
Módulos con nombre
Un módulo con nombre (explícito) es aquel que tiene un module-info.class, colocado en la ruta de módulos (--module-path / -p). Es el ciudadano de pleno derecho:
- Tiene un nombre proveniente de su descriptor.
- Lee únicamente los módulos que declara con
requires. - Expone únicamente los paquetes que declara con
exports.
Este es el módulo con encapsulación fuerte que describe el capítulo sobre la declaración de módulos. Todo lo que JPMS promete — dependencias declaradas, internos ocultos, resolución fallida anticipada — aplica a los módulos con nombre.
Módulos automáticos
Un módulo automático es un JAR ordinario (sin module-info) colocado en la ruta de módulos. JPMS lo envuelve en un módulo para que los módulos con nombre puedan declararlo con requires durante la migración — sin esperar a que el autor de la biblioteca añada un descriptor. Un módulo automático:
- Obtiene un nombre derivado del nombre del archivo JAR (por ejemplo,
guava-32.1.jar→guava), a menos que el manifiesto del JAR establezcaAutomatic-Module-Name. - Exporta todos sus paquetes — no tiene directiva
exports, por lo que todos sus paquetes están abiertos al mundo. - Lee todos los demás módulos, incluido el módulo sin nombre, para que pueda seguir viendo los JARs del classpath.
Es un puente: permite comenzar a escribir módulos con nombre que dependan de bibliotecas aún no modularizadas. El coste es que renuncia por completo a la encapsulación, y su nombre derivado automáticamente puede cambiar si se renombra el JAR — por eso Automatic-Module-Name en el manifiesto es lo responsable que una biblioteca debería incluir.
El módulo sin nombre
El módulo sin nombre es el comodín para el classpath. Cada clase cargada desde el classpath pertenece al módulo sin nombre de su cargador de clases. Este módulo:
- No tiene nombre (
getName()devuelvenull,isNamed()esfalse). - Lee todos los demás módulos del sistema.
- Exporta todos sus paquetes a otros módulos sin nombre/automáticos.
Pero existe una barrera unidireccional deliberada: un módulo con nombre no puede declarar requires del módulo sin nombre. No puedes nombrarlo, por lo que no puedes depender de él. Esta es la regla que impone el orden de migración — un módulo con nombre solo puede depender de otros módulos con nombre o automáticos, nunca de código en el classpath puro.
La matriz de acceso
Quién puede leer a quién se resume en una pequeña tabla:
| De ↓ / A → | Con nombre | Automático | Sin nombre |
|---|---|---|---|
| Con nombre | solo si requires | solo si requires | nunca |
| Automático | sí | sí | sí |
| Sin nombre | sí | sí | sí |
La única celda restrictiva — el código con nombre no puede acceder al código sin nombre — es la razón completa de por qué se migra de abajo hacia arriba (cubierto en el siguiente capítulo).
Un ejemplo práctico: identificar el tipo de un módulo en tiempo de ejecución
La API Module te indica, para cualquier clase, si su módulo tiene nombre y si fue sintetizado automáticamente. Este programa inspecciona tres referencias — su propia clase (classpath → sin nombre), un tipo del JDK (con nombre) y reporta la capa de arranque — para hacer concretas las categorías.
Lo que se puede deducir de la ejecución:
- La propia clase del programa se clasificó como UNNAMED con nombre
null, mientras quejava.util.ListyHttpClientse clasificaron como NAMED (java.base,java.net.http). Al ejecutarse desde el classpath, tu código siempre es sin nombre; el JDK siempre es un conjunto de módulos con nombre. El tipo de un módulo lo determina cómo fue cargado, no nada que esté en la clase en sí. java.base.canRead(self)devolviófalseperoself.canRead(java.base)devolviótrue. Esa es la barrera unidireccional en acción: el módulo sin nombre lee todo, pero ningún módulo con nombre lee el módulo sin nombre. Esta asimetría es precisamente por qué el código con nombre no puede declararrequiresde código del classpath.classify()distinguió automático de con nombre mediantedescriptor.isAutomatic(). No verástrueaquí (nada fue colocado en la ruta de módulos como JAR simple), pero la comprobación es exactamente cómo las herramientas reportan un módulo automático — un objeto de módulo real con un descriptor sintetizado y completamente abierto.isExported("java.util")devolviótrueperoisExported("jdk.internal.misc")devolviófalse, aunque ambos son paquetes reales dentro dejava.base. Losexportsde un módulo con nombre son una lista de permitidos; los paquetes no exportados (o solo exportados de forma calificada) son invisibles para el código externo sin importar que seanpublic. El módulo sin nombre, en cambio, exporta todo lo que contiene.- No se necesitó ningún
module-info.javapara observar nada de esto. Las tres categorías son hechos en tiempo de ejecución sobre cómo se cargó una clase, ygetModule()junto congetDescriptor()los exponen — las mismas llamadas en que se apoyan las herramientas de migración para determinar con qué están trabajando.
Por qué existen tres tipos
Los dos tipos de compatibilidad — automático y sin nombre — permiten que Java 9+ ejecute aplicaciones Java 8 sin modificar. Optas por la encapsulación fuerte un JAR a la vez: deja todo en el classpath (todo sin nombre) y nada cambia; mueve una biblioteca a la ruta de módulos sin descriptor y se vuelve automática; añade un module-info.java y se vuelve con nombre. A continuación, los servicios de módulos muestran el mecanismo uses/provides que desacopla los módulos, y la migración de módulos lleva un proyecto real a través de estos tres estados.