Нередко разработчики определяют не один, а несколько модулей, которые используются в программе. И при взаимодействии модулей есть некоторые особенности, которые следует учитывать. Рассмотрим, как модули могут взаимодействовать друг с другом.
Для выполнения своих задач одному модулю Java могут потребоваться пакеты из другого модуля. Однако если в модуля Java определены некоторые пакеты, они не являются автоматически доступными для других модулей. Модуль указывает, какие из его пакетов доступны, с помощью ключевого слова exports. Например, в объявлении модуля java.base, который предоставляет базовую функциональность Java и который мы можем посмотреть в репозитории OpenJDK на github, мы можем увидеть такие строки:
module java.base {
exports java.io;
exports java.lang;
exports java.lang.annotation;
exports java.lang.classfile;
...................................
exports java.util;
.........................
Таким образом, модуль "java.base" делает доступными такие пакеты как java.io, java.util и java.lang.
При экспорте пакета его открытые и защищенные классы и интерфейсы (с модификаторами доступа public и protected), а также их открытые и защищенные компоненты доступны вне модуля.
Другие же пакеты, которые явным образом не экспортируются с помощью оператора exports, скрыты от доступа из вне и недоступны вне своего модуля.
Кроме стандартных выражений экспорта с помощью оператора exports в исходном коде конфигурации модуля java.base можно увидеть такие строки как
exports jdk.internal.javac to
java.compiler,
jdk.compiler;
Выражения exports ... to представляют квалифицированный экспорт (qualified export). Он указывает, что только
перечисленные модули (java.compiler и jdk.compiler) могут получить доступ к экспортированному пакету, но другие модули не могут.
Для импорта функциональности модуля, как и пакетов и отдельных классов, применяется оператор import
import module java.base;
Это эквивалентно импорту всех экспортируемых пакетов модуля java.base. Например:
import module java.base;
class Program {
static void main(String[] args) {
List<String> people = new ArrayList<String>();
people.add("Tom");
people.add("Bob");
people.add("Sam");
System.out.println(people); // [Tom, Bob, Sam]
}
}
В данном случае мы явно не импортируем по отдельности типы List и ArrayList, ни их пакет. Однако импорт всего модуля приведет к тому, что эти типы также станут доступны
для использования, так как располагаются в пакете java.util из данного модуля.
Стоит отметить, что до Java 25 оболчка JShell импортировала специальный набор пакетов, а теперь импортирует модуль java.base, что и позволяет использовать в оболочки функциональность данного модуля
без какого-либо дополнительного импорта. Кроме того, в компактных программах модуль java.base также импортируется автоматически.
Вто же время при импорте модулей (особенно когда импортируется несколько модулей) возрастает вероятность конфликтов имен, если ряд модулей или даже пакетов одного модуля имеют одноименные типы. Поэтому, возможно, в ряде ситуаций традиционный импорт пакетов или отдельных типов будет более лучшим решением.
Определим проект со следующей структурой:
Здесь определено два модуля: demo и operations. Каждый располагается в своей одноименной папке.
В папке operations/com/metanit/factorial определен следующий файл Factorial.java:
package com.metanit.factorial;
public class Factorial{
public static int calculate(int n){
if (n < 1) return -1;
int result = 1;
while (n > 1) result *= n--;
return result;
}
}
Класс Factorial определяет функцию вычисления факториала и принадлежит пакету com.metanit.factorial.
Допустим, мы хотим, чтобы данный класс мог быть использован в других модулях. Но по умолчанию, все пакеты модуля невидимы для модулей извне. Чтобы сделать пакет, который определен в модуле, видимым для других модулей, этот пакет надо экспортировать при определении модуля с помощью оператора exports.
Для этого в папке operations определен следующий файл module-info.java:
module operations {
exports com.metanit.factorial;
}
Файл указывает, что данный модуль будет называться operations. С помощью оператора exports экспортируется
пакет com.metanit.factorial. Таким образом, другие модули смогут использовать класс Factorial.
Теперь перейдем к модулю demo. В папке demo определен следующий файл module-info.java:
module demo {
requires operations;
}
С помощью оператора requires< указываем, что модуль demo будет использовать модуль operations.
А в папке demo/com/metanit/hello/ определим следующий файл Hello.java:
package com.metanit.hello;
import com.metanit.factorial.Factorial;
public class Hello{
public static void main(String[] args){
int a = 5;
int b = Factorial.calculate(a);
System.out.printf("Factorial of %d is equal to %d \n", a, b);
}
}
Здесь мы подключаем класс Factorial из пакета "com.metanit/factorial" и вычисляем факториал числа.
В консоли перейдем к корневой папке, где размещаются оба модуля, и скомпилируем первый модуль - operations:
javac -d appmodules/operations operations/module-info.java operations/com/metanit/factorial/Factorial.java
С помощью параметра -d здесь указываем, что скомпилированный модуль будет помещаться в каталог appmodules/operations (если по умолчанию такого каталога нет, то он будет автоматически будет создан).
Скомпилируем второй модуль - demo, который использует первый модуль:
javac -p appmodules -d appmodules/demo demo/module-info.java demo/com/metanit/hello/Hello.java
Параметр -p представляет сокращение от --module-path и указывает, где искать используемые модули. То есть в данном случае это папка appmodules, куда ранее был помещен скомпилированный модуль operations.
Параметр -d, как и в случае выше, указывает, куда будет помещаться скомпилированный модуль - то есть в папку appmodules/demo.
После того, как все скомпилировано, запустим на выполнение модуль demo:
java -p appmodules -m demo/com.metanit.hello.Hello
При компиляции с помощью параметра -p указывается папка, где находятся оба скомпилированных модуля. В итоге на консоль будет выведен факториал числа:
eugene@Eugene:/java$ javac -d appmodules/operations operations/module-info.java operations/com/metanit/factorial/Factorial.java eugene@Eugene:/java$ javac -p appmodules -d appmodules/demo demo/module-info.java demo/com/metanit/hello/Hello.java eugene@Eugene:/java$ java -p appmodules -m demo/com.metanit.hello.Hello Factorial of 5 is equal to 120 eugene@Eugene:/java$