Java モジュールシステムに関するまとめ

投稿日: 

Java

B!
James OsborneによるPixabay(https://pixabay.com/)からの画像

こんにちは, Shinoryoです.

今回はJavaのモジュールシステムに関してまとめてみた結果を書き連ねていこうと思います.

モジュールシステム

  • モジュールシステム(module system):複数のパッケージをモジュールという単位でまとめることができるようになったもの.

目的

  • パッケージ単位の情報隠蔽を実現する.
  • アプリケーションモジュールとJDKのモジュールのリンクを容易に構築する.

モジュールの作成手順

  1. モジュール用のディレクトリを作成する.
  2. そのディレクトリに, module-info.javaというファイルを作る.
  3. 例えば次のようにコンパイルする:
    $ javac -d mods/moduleA src/moduleA/com/example/module-info.java src/moduleA/com/example/Main.java
    
    • mods/moduleA:クラスファイルの出力先ディレクトリ
    • src/moduleA/module-info.java:コンパイルするファイルのパス1
    • src/moduleA/com/example/a/Main.java:コンパイルするファイルのパス2

module-info.javaの記述内容

module-info.javaには次の2点を記述する. これにより, このモジュールがどのようなモジュールなのかを設定することができる.

  1. どのパッケージを公開するか(exports)
  2. 他のどのモジュールを使うのか(requires)
module moduleA {
    exports package1; // 公開するパッケージ名を記述
    exports package2; // 公開するパッケージが複数ある場合は, その個数だけexportsを記述する
    
    requires module1; // 利用するモジュール名を記述
    requires module2; // 利用するモジュールが複数ある場合は, その個数だけrequiresを記述する
}

モジュールの実行手順

例えば次のように実行する:

$ java --module-path mods -m moduleA/com.example.Main
  • mods:モジュールが存在するディレクトリ
  • moduleA:モジュール名
  • com.example.Main:実行したいクラスの完全修飾クラス名

モジュール間の依存関係

モジュールグラフ

  • モジュールグラフ(module graph):モジュール間の依存関係を表した図.
    • モジュールAがモジュールBに依存する場合, モジュールAからモジュールBに向かって矢印が引かれる.
moduleA
moduleA
moduleB
moduleB
moduleC
moduleC
Text is not SVG - cannot display

図1 モジュールグラフの例.

推移的な依存関係

  • 推移的な依存関係(transitive dependencies):自分が必要とするモジュールを, 自分を必要とするモジュールにも利用させること.

図1の依存関係において, module-info.javaには次のように記述される.

moduleAのmodule-info.java

module moduleA {
    requires moduleB;
}

moduleBのmodule-info.java

module moduleB {
    requires moduleC;
}

この場合だと, moduleAはmoduleCに属するクラスを利用することができない. moduleAがmoduleBを飛び越してmoduleCに属するクラスを利用するには, moduleBもmodule-info.javaを次のように変更する.

module moduleB {
    requires moduleC transitive;
}

相互依存によるコンパイルエラー

  • 相互依存:複数のモジュールが相互に利用しあっている状況.
    • コンパイラーが延々とそれぞれの設定ファイルを確認し続けることになるため, コンパイルエラーが発生する.

モジュールに関するコマンド

モジュールの情報を確認する

モジュールの情報を確認する方法は, 次の2つがある.

  1. javaコマンドの--describe-moduleオプションを使う.
    $ java --module-path mods --descrive-module moduleA
    
  2. jmodコマンドのdescribeモードを使う.
    $ jmod create --class-path mods/moduleA moduleA.jmod
    $ jmod describe moduleA.mod
    
  • JMODファイル形式:クラスファイルなどが1つのモジュールとしてまとまったファイル形式. ネイティブコードを含めることができるのが, JARファイル形式との大きな違いである.

モジュールの依存関係を確認する

モジュールの依存関係を確認する方法は, 次の2つがある.

  1. javaコマンドの--show-module-resolutionオプションを使う.
    $ java --module-path mods --show-module-resolution -m moduleA/com.example.a.Main
    
  2. jdepsコマンドを使う.
    $ jdeps mods/moduleA
    

非公開パッケージを一時的に利用する

  • javacコマンドの--add-exportsオプション:module-info.javaでexports宣言がされていないパッケージを, 一時的に利用してコンパイルするためのオプション.
    $ javac -d mods/moduleA --module-path mods --add-exports moduleB/com.example.b=moduleA src/moduleA/module-info.java src/moduleA/com/example/a/Main.java
    
    • 「--add-exports (対象のモジュール)/(公開するパッケージ)=(利用するモジュール)」の形式で記述する.
    • 緊急避難的に利用すべきものである. 乱用すると, パッケージの公開設定の制御が失われてしまう.

java.baseモジュール

  • java.baseモジュール:java.langパッケージ, java.utilパッケージ, java.ioパッケージなど, どのようなプログラムであっても使うであろうパッケージをまとめたもの.
    • 明示的にrequiresで宣言しなくても, 自動的に読み込まれる.
    • 明示的にrequiresで宣言しなかった場合, モジュールの情報の出力には代わりに次の行が含まれる:
module moduleA {
    // 略
    requires java.base mandated
    // 略
}

参考文献

  1. 志賀澄人. 徹底攻略 Java SE 11 Silver問題集 : [1Z0-815]対応. 東京, インプレス, 2019, p.371–392, 397, 412, 452, 453, 468, ISBN4-295-00762-5.
  2. Deitel, Paul. “Java 9 Modulesの理解”. Oracle. https://www.oracle.com/jp/corporate/features/understanding-java-9-modules.html, (参照 2023-11-24)
  3. tyab. “Java JMODファイルとは, JARファイルとの違い. ”. tyablog.net. 2020-04-05. https://tyablog.net/2020/04/05/java-how-different-jmod-and-jar/, (参照 2023-11-24).