1. Exportación de paquetes: ¿quién ve tu código?
En el mundo anterior a Java 9 todo era sencillo. Pero no muy seguro. Si una clase era public, cualquiera podía verla teniendo acceso al JAR o al classpath. Esto provocaba que clases internas e incluso paquetes enteros pudieran usarse accidental o deliberadamente fuera de tu proyecto. Sí, podías ocultar algo con private o package-private, pero si una clase es public — está disponible para todos.
Con la llegada de los módulos esto cambió. Ahora, incluso si una clase está declarada como public, no se puede usar desde otro módulo si el paquete en el que se encuentra no se ha exportado explícitamente mediante module-info.java.
Cómo se ve en la práctica
Supongamos que tienes la siguiente estructura de proyecto:
my-app/
src/
core/
com/example/core/
CoreService.java
InternalHelper.java
module-info.java
app/
com/example/app/
Main.java
module-info.java
En el archivo core/module-info.java escribes:
module core {
exports com.example.core;
}
Entonces solo las clases del paquete com.example.core estarán disponibles para otros módulos. Todo lo que esté en otros paquetes (por ejemplo, com.example.core.internal) será invisible, incluso si allí hay clases public. Esto es la encapsulación modular.
Mini demo: «visible/invisible»
Dentro de core:
// com/example/core/CoreService.java
package com.example.core;
public class CoreService {
public void doWork() {
System.out.println("Work done!");
}
}
Dentro de app:
// com/example/app/Main.java
package com.example.app;
import com.example.core.CoreService;
public class Main {
public static void main(String[] args) {
CoreService service = new CoreService();
service.doWork();
}
}
Si quitas la línea exports com.example.core; de core/module-info.java, al compilar obtendrás el error:
error: package com.example.core is not visible
¡Incluso si CoreService es public!
2. Importar dependencias: requires y aislamiento estricto
En JPMS no puedes simplemente usar una clase de otro módulo. Debes declarar explícitamente la dependencia con requires.
Ejemplo
En el archivo app/module-info.java:
module app {
requires core;
}
Ahora el módulo app puede usar todo lo que exporta el módulo core.
Si olvidas escribir requires core;, obtendrás un error de compilación:
error: package com.example.core is not visible
o
error: cannot access CoreService
Punto importante
- requires opera a nivel de módulos, no de paquetes.
- No puedes «importar» solo un paquete: solo el módulo exportado completo.
3. Comparativa: módulos frente a public/private
Los módulos añaden un nuevo nivel de encapsulación, que está «por encima» de las clases y los paquetes.
| Nivel | ¿Qué regula? | ¿Cómo funciona? |
|---|---|---|
| private | Acceso dentro de la clase | Solo dentro de un archivo |
| package-private (por defecto) | Acceso dentro del paquete | Todas las clases del mismo paquete |
| public | Acceso para todos | Cualquier código en cualquier parte |
| module | Acceso entre módulos | Solo los paquetes exportados |
Idea clave:
- Una clase puede ser public, pero si su paquete no está exportado, solo es accesible dentro del módulo.
- Exportar un paquete con exports es como una «ventana» por la que tu código es visible a otros módulos.
Ejemplo: implementación oculta
// com/example/core/internal/SecretSauce.java
package com.example.core.internal;
public class SecretSauce {
public void addMagic() {}
}
Si NO exportas el paquete com.example.core.internal en module-info.java, ningún módulo externo podrá usar esta clase, ¡aunque sea public!
4. Ejemplo: core y app — API e implementación
Veamos un escenario típico: el módulo core proporciona el API, el módulo app — lo utiliza.
Estructura:
core/
com/example/core/
CoreAPI.java
com/example/core/impl/
CoreImpl.java
module-info.java
app/
com/example/app/
Main.java
module-info.java
core/module-info.java:
module core {
exports com.example.core; // Solo API!
// No exportamos com.example.core.impl
}
app/module-info.java:
module app {
requires core;
}
com/example/core/CoreAPI.java:
package com.example.core;
public interface CoreAPI {
void doSomething();
}
com/example/core/impl/CoreImpl.java:
package com.example.core.impl;
import com.example.core.CoreAPI;
public class CoreImpl implements CoreAPI {
@Override
public void doSomething() {
System.out.println("Doing something!");
}
}
com/example/app/Main.java:
package com.example.app;
import com.example.core.CoreAPI;
// import com.example.core.impl.CoreImpl; // Esto provocará un error de compilación!
public class Main {
public static void main(String[] args) {
// CoreImpl impl = new CoreImpl(); // ¡Error! El paquete no está exportado.
// Solo se puede usar lo que es visible a través del API.
}
}
Resultado:
- El módulo app solo ve lo que exporta core.
- La implementación (impl) está oculta, incluso si las clases allí son public.
5. Práctica: jugar con export y la visibilidad
Paso 1: eliminar export
En core/module-info.java comenta la línea:
// exports com.example.core;
Ahora intenta compilar el proyecto. Espera un error de compilación en el módulo app: no verá las clases de com.example.core.
Paso 2: exportar solo el API
Vuelve a poner la línea exports com.example.core;, pero no exportes com.example.core.impl. Intenta en el módulo app importar una clase de impl. Volverás a obtener un error de compilación — ¡todo correcto!
Paso 3: clase public en un paquete no exportado
Crea una clase public en un paquete no exportado. Intenta usarla desde otro módulo — no funcionará. Eso es la encapsulación modular en acción.
6. Cómo se ve en el IDE y «en pocas palabras»
- Al intentar importar una clase desde un paquete no exportado, el IDE resaltará un error.
- La sugerencia explicará que el paquete no es exportado por el módulo.
- Añade exports para el paquete necesario y el error desaparecerá.
Esquema: niveles de acceso
+---------------------+
| MÓDULO |
| (module-info.java) |
+---------------------+
|
v
+---------------------+
| PAQUETES |
| (package, export) |
+---------------------+
|
v
+---------------------+
| CLASES |
| (public/private) |
+---------------------+
|
v
+---------------------+
| MÉTODOS/CAMPOS |
| (public/private) |
+---------------------+
7. Detalles y particularidades importantes
Exportación «solo para amigos»: exports ... to
A veces necesitas exportar un paquete solo para módulos concretos (por ejemplo, para tests o extensiones especiales):
exports com.example.core.internal to my.special.module, my.test.module;
Ahora solo los módulos indicados verán ese paquete.
¿Se pueden exportar varios paquetes?
¡Sí! Basta con añadir nuevas líneas exports:
exports com.example.core;
exports com.example.core.api;
¿Se puede exportar «todo»?
No. En el sistema modular de Java siempre indicas explícitamente qué exportar. Ahí reside su fortaleza.
8. Errores típicos al trabajar con la encapsulación modular
Error n.º 1: esperar que una clase public siempre sea visible.
Si el paquete no está exportado mediante module-info.java, la clase será invisible para otros módulos, incluso si es public. Es la «trampa» más frecuente para principiantes.
Error n.º 2: olvidar declarar requires.
Si tu módulo usa clases de otro módulo pero no escribes requires, obtendrás un error de compilación. No olvides describir explícitamente las dependencias.
Error n.º 3: intentar exportar el mismo paquete desde dos módulos.
En JPMS cada paquete solo puede ser exportado por un único módulo. Si violas esta regla, el compilador te detendrá.
Error n.º 4: estructura incorrecta — module-info.java en el lugar equivocado.
El archivo module-info.java debe estar en la raíz de los fuentes del módulo; de lo contrario, el módulo no será reconocido.
Error n.º 5: dependencias cíclicas entre módulos.
Si el módulo A requiere B y B requiere A — obtendrás un error. Evita ciclos en el grafo de dependencias.
GO TO FULL VERSION