未来エンジニア養成所Blog

プログラミングを皆に楽しんでもらうための情報をお届けします。

【Java】モジュールについて

Java9 から追加されたモジュールについて解説します。


モジュールとは

モジュールとは、パッケージを束ねるための単位です。
Java8まではクラスをパッケージに束ねていましたが、そのパッケージをさらに束ねるイメージです。


モジュールを利用すると、モジュール配下のパッケージに対するアクセス権限をより細かに設定できるようになります。


具体的には、

  • 現在のモジュールの中でだけpublic

  • 特定のモジュールに対してだけpublic

  • すべてのモジュールに対してpublic

のような権限をパッケージで設定できます。


たとえば次のような構成を考えてみましょう。

module

パッケージ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リファレンスでもそれぞれのパッケージ(クラス)が属するモジュールが確認できます。

API


モジュールの基本

モジュールを定義するには、ソースフォルダーのトップにモジュール定義ファイル(module-info.java)を配置するだけです。


eclipseを使用していると、プロジェクトを作成する際にあわせてモジュールも作成されます。


eclipse_module


eclipse_module2


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を設定します。

parent


chileプロジェクトを右クリックし、「ビルド・パス」→「ビルド・パスの構成」を選択します。 「モジュールパス」にparentを設定します。

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


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!


まとめ

今回はモジュールの概要について解説しました。


参考図書



さらに学習をしたい場合は、オンラインプログラミングスクール
未来エンジニア養成所Logo