1. Eksport pakietów: kto widzi twój kod?
W świecie przed Java 9 wszystko było proste. Tylko niezbyt bezpieczne. Jeśli klasa była public, każdy mógł ją zobaczyć, mając dostęp do JAR lub classpath. Prowadziło to do tego, że wewnętrzne klasy, a nawet całe pakiety mogły być przypadkowo lub celowo używane poza twoim projektem. Tak, można było coś ukryć za pomocą private lub package-private, ale jeśli klasa jest public — jest dostępna dla wszystkich.
Pojawienie się modułów to zmieniło. Teraz nawet jeśli klasa jest zadeklarowana jako public, nie można jej użyć z innego modułu, jeśli pakiet, w którym się znajduje, nie jest jawnie eksportowany przez module-info.java.
Jak to wygląda w praktyce
Załóżmy, że masz taką strukturę projektu:
my-app/
src/
core/
com/example/core/
CoreService.java
InternalHelper.java
module-info.java
app/
com/example/app/
Main.java
module-info.java
W pliku core/module-info.java piszesz:
module core {
exports com.example.core;
}
Wtedy tylko klasy z pakietu com.example.core będą dostępne dla innych modułów. Wszystko, co znajduje się w innych pakietach (na przykład com.example.core.internal), będzie niewidoczne — nawet jeśli są tam klasy public. To właśnie enkapsulacja modułowa.
Mini-demo: „widoczne/niewidoczne”
Wewnątrz core:
// com/example/core/CoreService.java
package com.example.core;
public class CoreService {
public void doWork() {
System.out.println("Work done!");
}
}
Wewnątrz 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();
}
}
Jeśli usuniesz linię exports com.example.core; z core/module-info.java, to przy kompilacji dostaniesz błąd:
error: package com.example.core is not visible
Nawet jeśli CoreService jest public!
2. Import zależności: requires i ścisła izolacja
W JPMS nie można ot tak użyć klasy z innego modułu. Trzeba jawnie zadeklarować zależność przez requires.
Przykład
W pliku app/module-info.java:
module app {
requires core;
}
Teraz moduł app może używać wszystkiego, co eksportuje moduł core.
Jeśli zapomnisz dodać requires core;, dostaniesz błąd kompilacji:
error: package com.example.core is not visible
albo
error: cannot access CoreService
Ważna uwaga
- requires działa na poziomie modułów, a nie pakietów.
- Nie możesz „zaimportować” tylko jednego pakietu — jedynie cały eksportowany moduł.
3. Porównanie: moduły kontra public/private
Moduły dodają nowy poziom enkapsulacji, który znajduje się „nad” klasami i pakietami.
| Poziom | Co kontroluje? | Jak działa? |
|---|---|---|
| private | Dostęp wewnątrz klasy | Tylko w obrębie jednego pliku |
| package-private (domyślnie) | Dostęp w obrębie pakietu | Wszystkie klasy w jednym pakiecie |
| public | Dostęp dla wszystkich | Dowolny kod w dowolnym miejscu |
| module | Dostęp między modułami | Tylko eksportowane pakiety |
Kluczowa idea:
- Klasa może być public, ale jeśli jej pakiet nie jest eksportowany, jest dostępna tylko wewnątrz modułu.
- Eksport pakietu przez exports to jak „okno”, przez które twój kod jest widoczny dla innych modułów.
Przykład: ukryta implementacja
// com/example/core/internal/SecretSauce.java
package com.example.core.internal;
public class SecretSauce {
public void addMagic() {}
}
Jeśli NIE eksportujesz pakietu com.example.core.internal w module-info.java, żaden zewnętrzny moduł nie będzie mógł użyć tej klasy, nawet jeśli jest public!
4. Przykład: core i app — API i implementacja
Rozważmy typowy scenariusz: moduł core dostarcza API, moduł app — używa go.
Struktura:
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; // Tylko API!
// Nie eksportujemy 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; // To wywoła błąd kompilacji!
public class Main {
public static void main(String[] args) {
// CoreImpl impl = new CoreImpl(); // Błąd! Pakiet nie jest eksportowany.
// Można używać tylko tego, co jest widoczne przez API.
}
}
Rezultat:
- Moduł app widzi tylko to, co eksportuje core.
- Implementacja (impl) jest ukryta, nawet jeśli znajdują się tam klasy public.
5. Praktyka: ćwiczymy exports i widoczność
Krok 1: usuwamy exports
W core/module-info.java zakomentuj linię:
// exports com.example.core;
Teraz spróbuj skompilować projekt. Oczekuj błędu kompilacji w module app — nie zobaczy on klas z com.example.core.
Krok 2: eksportujemy tylko API
Przywróć linię exports com.example.core;, ale nie eksportuj com.example.core.impl. Spróbuj w module app zaimportować klasę z impl. Znów dostaniesz błąd kompilacji — wszystko uczciwie!
Krok 3: klasa publiczna w nieeksportowanym pakiecie
Utwórz klasę public w nieeksportowanym pakiecie. Spróbuj użyć jej z innego modułu — nie uda się. To właśnie enkapsulacja modułowa w działaniu.
6. Jak to wygląda w IDE i „na palcach”
- Przy próbie zaimportowania klasy z nieeksportowanego pakietu IDE podkreśli błąd.
- Podpowiedź wyjaśni, że pakiet nie jest eksportowany przez moduł.
- Dodaj exports dla potrzebnego pakietu — błąd zniknie.
Schemat: poziomy dostępu
+---------------------+
| MODUŁ |
| (module-info.java) |
+---------------------+
|
v
+---------------------+
| PAKIETY |
| (package, exports) |
+---------------------+
|
v
+---------------------+
| KLASY |
| (public/private) |
+---------------------+
|
v
+---------------------+
| METODY/POLA |
| (public/private) |
+---------------------+
7. Ważne niuanse i szczegóły
Eksport „tylko dla przyjaciół”: exports ... to
Czasem trzeba eksportować pakiet tylko dla określonych modułów (np. dla testów lub specjalnych rozszerzeń):
exports com.example.core.internal to my.special.module, my.test.module;
Teraz tylko wskazane moduły zobaczą ten pakiet.
Czy można eksportować kilka pakietów?
Tak! Po prostu dodawaj kolejne linie exports:
exports com.example.core;
exports com.example.core.api;
Czy można eksportować „wszystko”?
Nie. W modularnym systemie Javy zawsze jawnie wskazujesz, co eksportować. Na tym polega jego siła.
8. Typowe błędy przy pracy z enkapsulacją modułową
Błąd nr 1: Oczekiwanie, że klasa public zawsze jest widoczna.
Jeśli pakiet nie jest eksportowany przez module-info.java, klasa będzie niewidoczna dla innych modułów, nawet jeśli jest public. To najczęstsza „pułapka” dla początkujących.
Błąd nr 2: Zapomniano zadeklarować requires.
Jeśli twój moduł używa klas z innego modułu, ale nie dodał requires, dostaniesz błąd kompilacji. Pamiętaj, aby jawnie opisywać zależności.
Błąd nr 3: Próba eksportu tego samego pakietu z dwóch modułów.
W JPMS każdy pakiet może być eksportowany tylko przez jeden moduł. Jeśli złamiesz tę zasadę, kompilator cię zatrzyma.
Błąd nr 4: Naruszenie struktury — module-info.java w złym miejscu.
Plik module-info.java musi leżeć w katalogu źródeł modułu (korzeniu jego źródeł), w przeciwnym razie moduł nie zostanie rozpoznany.
Błąd nr 5: Cykliczne zależności między modułami.
Jeśli moduł A wymaga B, a B wymaga A — dostaniesz błąd. Unikaj cykli w grafie zależności.
GO TO FULL VERSION