Java9 から追加されたモジュールについて解説します。
モジュールとは
モジュールとは、パッケージを束ねるための単位です。
Java8まではクラスをパッケージに束ねていましたが、そのパッケージをさらに束ねるイメージです。
モジュールを利用すると、モジュール配下のパッケージに対するアクセス権限をより細かに設定できるようになります。
具体的には、
現在のモジュールの中でだけpublic
特定のモジュールに対してだけpublic
すべてのモジュールに対してpublic
のような権限をパッケージで設定できます。
たとえば次のような構成を考えてみましょう。
パッケージAに含まれるMyUseクラスが、実行用のクラスです。
このクラスはパッケージBのMyServiceクラスを参照しています。
パッケージCにはMyServiceで使うライブラリがまとめられているイメージで、MyUtilクラスをパッケージBには公開しますが、パッケージAには非公開としたいイメージです。
このような場合、MyUseクラスでMyServiceクラスを使用できるように、MyServiceクラスをpublicで宣言します。
さらに、MyServiceクラスでMyUtilクラスを使用できるように、MyUtilクラスをpublicで宣言します。
とすると、MyUseクラスからもMyUtilクラスが使用できる(本来は非公開にしたい)ようになってしまうのです。
このようなケースで適切に権限設定ができるようになったのが「モジュール」です。
標準ライブラリは?
Java 9以降では、標準ライブラリもすべてモジュール化されました。
以下のコマンドで確認してみましょう。
(私の環境はJava11です)
$ java --list-modules java.base@11.0.9 java.compiler@11.0.9 java.datatransfer@11.0.9 java.desktop@11.0.9 java.instrument@11.0.9 java.logging@11.0.9 java.management@11.0.9 java.management.rmi@11.0.9 java.naming@11.0.9 java.net.http@11.0.9 java.prefs@11.0.9 java.rmi@11.0.9 java.scripting@11.0.9 java.se@11.0.9 java.security.jgss@11.0.9 java.security.sasl@11.0.9 java.smartcardio@11.0.9 java.sql@11.0.9 java.sql.rowset@11.0.9 java.transaction.xa@11.0.9 java.xml@11.0.9 java.xml.crypto@11.0.9 jdk.accessibility@11.0.9 jdk.aot@11.0.9 jdk.attach@11.0.9 jdk.charsets@11.0.9 jdk.compiler@11.0.9 jdk.crypto.cryptoki@11.0.9 jdk.crypto.ec@11.0.9 jdk.dynalink@11.0.9 jdk.editpad@11.0.9 jdk.hotspot.agent@11.0.9 jdk.httpserver@11.0.9 jdk.internal.ed@11.0.9 jdk.internal.jvmstat@11.0.9 jdk.internal.le@11.0.9 jdk.internal.opt@11.0.9 jdk.internal.vm.ci@11.0.9 jdk.internal.vm.compiler@11.0.9 jdk.internal.vm.compiler.management@11.0.9 jdk.jartool@11.0.9 jdk.javadoc@11.0.9 jdk.jcmd@11.0.9 jdk.jconsole@11.0.9 jdk.jdeps@11.0.9 jdk.jdi@11.0.9 jdk.jdwp.agent@11.0.9 jdk.jfr@11.0.9 jdk.jlink@11.0.9 jdk.jshell@11.0.9 jdk.jsobject@11.0.9 jdk.jstatd@11.0.9 jdk.localedata@11.0.9 jdk.management@11.0.9 jdk.management.agent@11.0.9 jdk.management.jfr@11.0.9 jdk.naming.dns@11.0.9 jdk.naming.ldap@11.0.9 jdk.naming.rmi@11.0.9 jdk.net@11.0.9 jdk.pack@11.0.9 jdk.rmic@11.0.9 jdk.scripting.nashorn@11.0.9 jdk.scripting.nashorn.shell@11.0.9 jdk.sctp@11.0.9 jdk.security.auth@11.0.9 jdk.security.jgss@11.0.9 jdk.unsupported@11.0.9 jdk.unsupported.desktop@11.0.9 jdk.xml.dom@11.0.9 jdk.zipfs@11.0.9
また、APIリファレンスでもそれぞれのパッケージ(クラス)が属するモジュールが確認できます。
モジュールの基本
モジュールを定義するには、ソースフォルダーのトップにモジュール定義ファイル(module-info.java)を配置するだけです。
eclipseを使用していると、プロジェクトを作成する際にあわせてモジュールも作成されます。
module-info.java
module phoeducation { }
これでphoeducationモジュールを定義したことになります。
初期の状態では、{…}の中身は空ですが、最低限モジュールを定義するだけであればこれで十分です。
この後、「依存するモジュール」「外部に公開するパッケージ」などを設定する際には、{…}配下にコードを記述していくことになります。
モジュールを使ってみる
プロジェクトの作成
下記3つのプロジェクトを作成します。
grandParent
parent
child
モジュールを作成
各プロジェクトに「module-info.java」ファイルを作成します。
(この時点では{…}の中身は空のままです)
クラスを作成
各プロジェクトにクラスを作成します。
grandParentプロジェクト
HelloGrandParent.java
package grandParent; public class HelloGrandParent { public void hello() { System.out.print("Hello! GrandParent!"); } }
parentプロジェクト
ParentMain.java
package parent; import grandParent.HelloGrandParent; public class ParentMain { public static void main(String[] args) { HelloGrandParent hgp = new HelloGrandParent(); hgp.hello(); } }
HelloParent.java
package parent; public class HelloParent { public void hello() { System.out.println("Hello! Parent!"); } }
childプロジェクト
ChildMain.java
package child; import parent.HelloParent; public class ChildMain { public static void main(String[] args) { HelloParent hp = new HelloParent(); hp.hello(); } }
プロジェクトにモジュールパスを設定
eclipseのビルド・パス設定で、モジュールパスを設定します。
parentプロジェクトを右クリックし、「ビルド・パス」→「ビルド・パスの構成」を選択します。
「モジュールパス」にgrandParentを設定します。
chileプロジェクトを右クリックし、「ビルド・パス」→「ビルド・パスの構成」を選択します。
「モジュールパス」にparentを設定します。
module-info.javaを編集
grandParentプロジェクトのパッケージを外部に公開し、parentプロジェクトで読み込めるように設定します。
grandParentプロジェクトのmodule-info.java
module grandParent {
//grandParentプロジェクトを他のパッケージからアクセスできるようにする
exports grandParent;
}
exports宣言(配下のパッケージを公開)
モジュール配下のパッケージは、デフォルトでモジュールプライベート(=モジュールの外からはアクセスできない)と見なされます。
モジュールの外からパッケージへのアクセスを許可するには、exportsで公開したいパッケージを宣言する必要があります。
parentプロジェクトのmodule-info.java
module parent {
//grandParentモジュールを読み込む
requires grandParent;
}
requires宣言(他のモジュールを利用する)
モジュールの世界では、デフォルトでは異なるモジュールにはアクセスできません。
別のモジュールを利用したい場合には、モジュール定義ファイルにrequires宣言を必要があります。
ParentMain.javaを実行するとgrandParentプロジェクトのHelloGrandParent()メソッドが実行できるようになった事が確認できます。
Hello! GrandParent!
今度はparentプロジェクトのパッケージを外部に公開し、childプロジェクトで読み込めるように設定します。
parentプロジェクトのmodule-info.java
module parent { //grandParentモジュールを読み込む requires grandParent; //parentプロジェクトを他のパッケージからアクセスできるようにする exports parent; }
childプロジェクトのmodule-info.java
module child {
//parentモジュールを読み込む
requires parent;
}
ChildMain.javaを実行するとparentプロジェクトのHelloParent()メソッドが実行できるようになった事が確認できます。
Hello! Parent!
モジュールを間接的に読み込む
このままではchildプロジェクトでgrandParentモジュールを使用することはできないため、childプロジェクトでもgrandParentモジュールを使用できるように設定します。
まずはeclipseのビルド・パス設定で、モジュールパスを設定します。
childプロジェクトを右クリックし、「ビルド・パス」→「ビルド・パスの構成」を選択します。
「モジュールパス」にgrandParentを設定します。
parentモジュールで読み込んでいるgrandParentモジュールをchildプロジェクトでも使用できるようにするには「requires transitive」を記述します。
parentプロジェクトのmodule-info.java
module parent { //grandParentモジュールを読み込む //requires grandParent; ←コメントアウトしておきます。 //parentプロジェクトを他のパッケージからアクセスできるようにする exports parent; //parentモジュールを読み込んだ場合はgrandParentモジュールも使えるようにする requires transitive grandParent; }
requires transitive宣言(推移的な依存関係を宣言する)
モジュールが利用している先のモジュールで利用しているモジュール(=推移的な依存)requires transitive宣言で表現することができます。
ChildMain.java
package child; import grandParent.HelloGrandParent; import parent.HelloParent; public class ChildMain { public static void main(String[] args) { HelloParent hp = new HelloParent(); hp.hello(); //grandParentプロジェクトのメソッド HelloGrandParent hgp = new HelloGrandParent(); hgp.hello(); } }
ChildMain.javaを実行するとgrandParentプロジェクトのHelloGrandParent()メソッドも実行できるようになった事が確認できます。
Hello! Parent! Hello! GrandParent!
まとめ
今回はモジュールの概要について解説しました。
参考図書