Module (module-info.java) în Java

Un modul (module-info.java) grupează pachete și declară explicit de ce module depinde (requires) și ce pachete oferă altora (exports).

Sintaxă
// fișierul module-info.java, în rădăcina surselor modulului
module ro.dexblog.meteo {
    requires java.net.http;          // de ce module am nevoie
    exports ro.dexblog.meteo;        // ce pachete pot folosi alții
}

Ce face Module (module-info.java)

Modulele, apărute în Java 9, sunt un nivel peste pachete. Un modul are un nume și un fișier module-info.java care spune două lucruri: ce alte module îi trebuie (requires) și ce pachete ale lui pot fi folosite din afară (exports). Pachetele neexportate rămân interne, chiar dacă au clase public: alt modul nu le poate importa.

Însuși JDK-ul este împărțit în module: java.base (java.lang, java.util, java.io; inclus automat), java.net.http, java.sql, java.desktop și altele. java --list-modules le afișează. Alte directive: requires transitive, opens (acces prin reflecție, pentru framework-uri), uses/provides (servicii).

Modulele sunt opționale. Programele fără module-info.java, cum sunt toate exemplele de pe această secțiune, rulează în „modulul fără nume” (unnamed module), care vede tot ce exportă JDK-ul. Modulele sunt utile în aplicații și biblioteci mari și pentru jlink, care construiește un runtime Java micșorat, doar cu modulele necesare.

Exemple

Exemplul 1: Două module: o bibliotecă și o aplicație

Modulul ro.dexblog.meteo exportă doar pachetul ro.dexblog.meteo; pachetul intern rămâne ascuns. Aplicația declară requires ro.dexblog.meteo. Se compilează cu --module-source-path și se rulează cu --module-path și -m modul/clasă.

// src/ro.dexblog.meteo/module-info.java
module ro.dexblog.meteo {
    exports ro.dexblog.meteo;
}

// src/ro.dexblog.meteo/ro/dexblog/meteo/Prognoza.java
package ro.dexblog.meteo;

import ro.dexblog.meteo.intern.Senzori;

public class Prognoza {
    public static String pentru(String oras) {
        return oras + ": " + Senzori.temperatura(oras) + " °C";
    }
}

// src/ro.dexblog.meteo/ro/dexblog/meteo/intern/Senzori.java
package ro.dexblog.meteo.intern;

public class Senzori {
    public static int temperatura(String oras) {
        return oras.equals("Iași") ? 14 : 11;
    }
}

// src/ro.dexblog.app/module-info.java
module ro.dexblog.app {
    requires ro.dexblog.meteo;
}

// src/ro.dexblog.app/ro/dexblog/app/Main.java
package ro.dexblog.app;

import ro.dexblog.meteo.Prognoza;

public class Main {
    public static void main(String[] args) {
        System.out.println(Prognoza.pentru("Iași"));
        System.out.println(Prognoza.pentru("Suceava"));
        System.out.println("modul: " + Main.class.getModule().getName());
    }
}

Rezultat în terminal

$ javac -d out --module-source-path src -m ro.dexblog.app
$ java --module-path out -m ro.dexblog.app/ro.dexblog.app.Main
Iași: 14 °C
Suceava: 11 °C
modul: ro.dexblog.app

Exemplul 2: Un pachet neexportat nu poate fi importat

Dacă aplicația încearcă să importe ro.dexblog.meteo.intern.Senzori, compilarea eșuează, deși clasa este public: modulul bibliotecii nu exportă pachetul intern.

// src/ro.dexblog.app/ro/dexblog/app/Main.java
package ro.dexblog.app;

import ro.dexblog.meteo.Prognoza;
import ro.dexblog.meteo.intern.Senzori;   // pachet neexportat

public class Main {
    public static void main(String[] args) {
        System.out.println(Prognoza.pentru("Iași"));
        System.out.println(Senzori.temperatura("Cluj"));
    }
}

Rezultat în terminal

$ javac -d out --module-source-path src -m ro.dexblog.app
src/ro.dexblog.app/ro/dexblog/app/Main.java:4: error: package ro.dexblog.meteo.intern does not exist
import ro.dexblog.meteo.intern.Senzori;
                              ^
1 error

Exemplul 3: Modulele văzute din cod

Programul acesta nu are module-info.java, deci clasa Main este în modulul fără nume. String vine din java.base, iar HttpClient din java.net.http.

import java.net.http.HttpClient;

public class Main {
    public static void main(String[] args) {
        Module meu = Main.class.getModule();
        System.out.println("Main are modul cu nume? " + meu.isNamed());
        System.out.println("String: " + String.class.getModule().getName());
        System.out.println("HttpClient: " + HttpClient.class.getModule().getName());

        Module baza = Object.class.getModule();
        System.out.println("java.base exportă java.util? " + baza.isExported("java.util"));
        System.out.println("java.base exportă jdk.internal.misc? " + baza.isExported("jdk.internal.misc"));
    }
}

Rezultat

Main are modul cu nume? false
String: java.base
HttpClient: java.net.http
java.base exportă java.util? true
java.base exportă jdk.internal.misc? false

Greșeli frecvente

  • Pui module-info.java în proiect și uiți requires pentru un modul JDK: package java.net.http is not visible. Fără module-info, toate modulele standard sunt vizibile.
  • Uiți exports pentru pachetul pe care vrei să îl folosească alții: pentru celelalte module, pachetul nu există.
  • Folosești reflecție (de exemplu un framework care citește câmpuri private) pe un pachet doar exportat. Pentru reflecție profundă e nevoie de opens.
  • Rulezi un modul cu -cp în loc de --module-path: module-info este ignorat și clasele rulează în modulul fără nume.

Comenzi înrudite

  • Pachete și importuri (package, import): package așază clasa într-un pachet (un spațiu de nume, ca un director); import permite folosirea claselor din alte pachete după numele scurt.
  • Modificatori de acces (public, protected, private): public, protected, private și lipsa modificatorului stabilesc cine poate vedea o clasă, un câmp, o metodă sau un constructor.
  • javac: javac este compilatorul Java: transformă fișierele .java în fișiere .class (bytecode) și raportează erorile și avertismentele.
  • java (lansatorul): java pornește mașina virtuală Java și rulează un program: o clasă compilată, un fișier .jar sau direct un fișier sursă .java.
  • jar: jar împachetează clasele compilate (și resursele) într-o singură arhivă .jar, care poate fi distribuită și rulată cu java -jar.