1. Esportazione dei pacchetti: chi vede il tuo codice?
Nel mondo prima di Java 9 era tutto semplice, ma non molto sicuro. Se una classe era public, chiunque poteva vederla avendo accesso al JAR o al classpath. Questo portava al fatto che classi interne e persino interi pacchetti potessero essere usati accidentalmente o intenzionalmente al di fuori del tuo progetto. Sì, si poteva nascondere qualcosa con private o package-private, ma se una classe è public, è accessibile a tutti.
Con i moduli, questo è cambiato. Ora, anche se una classe è dichiarata public, non la si può usare da un altro modulo se il pacchetto in cui si trova non è esportato esplicitamente tramite module-info.java.
Come appare nella pratica
Supponiamo che tu abbia la seguente struttura del progetto:
my-app/
src/
core/
com/example/core/
CoreService.java
InternalHelper.java
module-info.java
app/
com/example/app/
Main.java
module-info.java
Nel file core/module-info.java scrivi:
module core {
exports com.example.core;
}
Allora solo le classi del pacchetto com.example.core saranno disponibili agli altri moduli. Tutto ciò che si trova in altri pacchetti (per esempio, com.example.core.internal) sarà invisibile — anche se lì ci sono classi public. Questo è l’incapsulamento modulare.
Mini demo: « visibile/invisibile »
All'interno di core:
// com/example/core/CoreService.java
package com.example.core;
public class CoreService {
public void doWork() {
System.out.println("Work done!");
}
}
All'interno di 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();
}
}
Se rimuovi la riga exports com.example.core; da core/module-info.java, in compilazione otterrai l’errore:
error: package com.example.core is not visible
Anche se CoreService è public!
2. Import delle dipendenze: requires e isolamento rigoroso
In JPMS non puoi semplicemente usare una classe da un altro modulo. Devi dichiarare esplicitamente la dipendenza con requires.
Esempio
Nel file app/module-info.java:
module app {
requires core;
}
Ora il modulo app può usare tutto ciò che il modulo core esporta.
Se dimentichi di scrivere requires core;, otterrai un errore di compilazione:
error: package com.example.core is not visible
oppure
error: cannot access CoreService
Punto importante
- requires funziona a livello di moduli, non di pacchetti.
- Non puoi «importare» un solo pacchetto — solo l'intero modulo esportato.
3. Confronto: moduli vs public/private
I moduli aggiungono un nuovo livello di incapsulamento che sta «sopra» classi e pacchetti.
| Livello | Cosa regola? | Come funziona? |
|---|---|---|
| private | Accesso all'interno della classe | Solo all'interno di un file |
| package-private (predefinito) | Accesso all'interno del pacchetto | Tutte le classi nello stesso pacchetto |
| public | Accesso per tutti | Qualsiasi codice in qualsiasi punto |
| module | Accesso tra moduli | Solo i pacchetti esportati |
Idea chiave:
- Una classe può essere public, ma se il suo pacchetto non è esportato è accessibile solo all'interno del modulo.
- Esportare un pacchetto con exports è come una «finestra» attraverso cui il tuo codice è visibile ad altri moduli.
Esempio: implementazione nascosta
// com/example/core/internal/SecretSauce.java
package com.example.core.internal;
public class SecretSauce {
public void addMagic() {}
}
Se NON esporti il pacchetto com.example.core.internal in module-info.java, nessun modulo esterno potrà usare questa classe, anche se è public!
4. Esempio: core e app — API e implementazione
Consideriamo uno scenario tipico: il modulo core fornisce l’API, il modulo app la utilizza.
Struttura:
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!
// Non esportiamo 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; // Questo causerà un errore di compilazione!
public class Main {
public static void main(String[] args) {
// CoreImpl impl = new CoreImpl(); // Errore! Il pacchetto non è esportato.
// Si può usare solo ciò che è visibile tramite l'API.
}
}
Risultato:
- Il modulo app vede solo ciò che core esporta.
- L’implementazione (impl) è nascosta, anche se le classi lì sono public.
5. Pratica: giochiamo con export e visibilità
Passo 1: rimuovere l'export
In core/module-info.java commenta la riga:
// exports com.example.core;
Ora prova a compilare il progetto. Aspettati un errore di compilazione nel modulo app — non vedrà le classi da com.example.core.
Passo 2: esportare solo l'API
Ripristina la riga exports com.example.core;, ma non esportare com.example.core.impl. Prova nel modulo app a importare una classe da impl. Otterrai di nuovo un errore di compilazione — tutto regolare!
Passo 3: classe public in un pacchetto non esportato
Crea una classe public in un pacchetto non esportato. Prova a usarla da un altro modulo — non funzionerà. Questo è l’incapsulamento modulare in azione.
6. Come appare in un IDE e « in parole semplici »
- Quando provi a importare una classe da un pacchetto non esportato, l’IDE evidenzierà un errore.
- Il suggerimento spiegherà che il pacchetto non è esportato dal modulo.
- Aggiungi exports per il pacchetto necessario — l’errore scomparirà.
Schema: livelli di accesso
+---------------------+
| MODULO |
| (module-info.java) |
+---------------------+
|
v
+---------------------+
| PACCHETTI |
| (package, export) |
+---------------------+
|
v
+---------------------+
| CLASSI |
| (public/private) |
+---------------------+
|
v
+---------------------+
| METODI/CAMPI |
| (public/private) |
+---------------------+
7. Dettagli importanti e particolarità
Export «solo per gli amici»: exports ... to
A volte è necessario esportare un pacchetto solo per moduli specifici (per esempio per test o estensioni speciali):
exports com.example.core.internal to my.special.module, my.test.module;
Ora solo i moduli indicati vedranno questo pacchetto.
Si possono esportare più pacchetti?
Sì! Basta aggiungere nuove righe exports:
exports com.example.core;
exports com.example.core.api;
Si può esportare «tutto»?
No. Nel sistema di moduli di Java specifichi sempre esplicitamente cosa esportare. È questa la sua forza.
8. Errori tipici quando si lavora con l'incapsulamento modulare
Errore n. 1: aspettarsi che una classe public sia sempre visibile.
Se il pacchetto non è esportato tramite module-info.java, la classe sarà invisibile per gli altri moduli, anche se è public. È la «trappola» più comune per i principianti.
Errore n. 2: hai dimenticato di dichiarare requires.
Se il tuo modulo usa classi da un altro modulo ma non ha scritto requires, otterrai un errore di compilazione. Ricordati di descrivere esplicitamente le dipendenze.
Errore n. 3: tentare di esportare lo stesso pacchetto da due moduli.
In JPMS ogni pacchetto può essere esportato da un solo modulo. Se violi questa regola, il compilatore ti fermerà.
Errore n. 4: struttura errata — module-info.java nel posto sbagliato.
Il file module-info.java deve trovarsi nella radice dei sorgenti del modulo, altrimenti il modulo non verrà riconosciuto.
Errore n. 5: dipendenze cicliche tra moduli.
Se il modulo A richiede B e B richiede A — otterrai un errore. Evita i cicli nel grafo delle dipendenze.
GO TO FULL VERSION