モジュールの必要性
Javaにおけるモジュールとは、複数のパッケージをまとめてひとつの単位とし、外部に公開するパッケージと非公開にするパッケージを分けて管理する仕組みである。そもそもなぜモジュールという単位が必要なのかを理解する。
パッケージ分割の限界
freshfoodパッケージに、public
なTomato,Carrotクラスと、デフォルト(package private
)のApple,Bananaクラスがあるとする。このfreshfoodパッケージ内をさらにvegetableパッケージとfruitパッケージに分割する場合を考える。
ここで問題となるのが、パッケージを分割したことでクラスの可視性が変わってしまうということである。vegetableパッケージのTomato,Carrotクラスが、Apple,Bananaクラスと別のパッケージになったことで package private なクラスへはアクセスできなくなった。
Apple,Bananaクラスをfreshfood内でアクセス可能にするためには public
にする必要があるが、それは同時にfreshfood外のパッケージからもアクセス可能にしてしまうことにもなる。このようにパッケージ分割では、パッケージの内部と外部で公開/非公開を切り替えることができない。
ひとつのまとまりの内部では公開にしたいが外部からは非公開にしたいとき、その「まとまり」をパッケージとしてではなく、モジュールとして扱う。
つまりモジュールとは、複数のパッケージをまとめた単位のことで、public なクラスをモジュール内では自由にアクセス可能とし、モジュール外からは隠蔽することができる機能を持つ。
モジュールでできること
①外部に公開したいパッケージを設定できる
外部に公開したいパッケージ名をmodule-info.javaファイルに exports パッケージ名;
として記述することで、モジュール外からそのパッケージ内の public クラスにアクセス可能となる。
exports に記述しなかったパッケージは、たとえクラスが public であってもモジュール外からはアクセスできない。これによりモジュール内部には公開され、モジュール外部には非公開という構造を実現でき、安全で保守しやすい設計につながる。
②モジュールごとの依存関係を設定できる
外部のモジュールの機能を利用したいときは、module-info.javaに requires 利用するモジュール名;
と記述する。
モジュールの作成と実行
モジュールの作成手順
① モジュール用ディレクトリを作成し、クラスをパッケージごとに配置する
② ①の直下に module-info.java を作成する
③ モジュールをコンパイルする
④ モジュールを実行する
以下のような構成のプロジェクトがあるとする。
supermarket ← プロジェクト
└── src
└── freshfood ← パッケージ
├── Tomato.java
├── Carrot.java
├── Apple.java
└── Banana.java
ここでfreshfoodパッケージを vegetable, fruitパッケージに分割してまとめた freshfoodモジュールを作成する場合を考える。
それぞれの工程について詳しく見ていく。
① モジュール用ディレクトリを作成し、クラスをパッケージごとに配置する
まずモジュールのルートディレクトリ(src)に モジュール用のディレクトリ(freshfood)を配置する。
supermarket
└── src ← モジュールのルートディレクトリ
└── freshfood ← モジュール用ディレクトリ
└── com/example
|
├── vegetable ← 分割したパッケージ
│ ├── Tomato.java
│ └── Carrot.java
└── fruit ← 分割したパッケージ
├── Apple.java
└── Banana.java
② モジュール用ディレクトリの直下に module-info.java を作成する
①で作成した freshfoodディレクトリ直下にモジュール定義ファイル(module-info.java)を作成する。これはモジュールの宣言と設定をするためのファイルで、コンパイル時は「module-info.javaを直下に持つディレクトリ」がモジュールであると認識される。
module-info.javaには以下の2つを記述する。
-
exports
公開するパッケージ名 -
requires
使用したい外部のモジュール名
vegetableパッケージのみを公開したい場合は、以下のように記述する。
module freshfood {
exports com.example.vegetable;
}
モジュール作成後のディレクトリ構成は以下のようになる。
supermarket
└── src
└── freshfood ← モジュール用ディレクトリ
├── module-info.java ← モジュール宣言ファイル
└── com/example
├── vegetable ← 公開パッケージ
│ ├── Tomato.java
│ └── Carrot.java
└── fruit ← 非公開パッケージ
├── Apple.java
└── Banana.java
③モジュールをコンパイルする
出力先ディレクトリの用意
コンパイル結果を出力するための mods ディレクトリを作成し、その中にモジュール名(ここでは freshfood)のディレクトリを作成する。mods は src と同じ階層に配置するのが一般的である。
supermarket
├── src
│ └── freshfood ← モジュール用ディレクトリ(ソース)
│ ├── module-info.java ← モジュール宣言ファイル
│ └── com/example
│ ├── vegetable ← 公開パッケージ
│ │ ├── Tomato.java
│ │ └── Carrot.java
│ └── fruit ← 非公開パッケージ
│ ├── Apple.java
│ └── Banana.java
└── mods
└── freshfood ← コンパイル後にモジュールを出力するためのファイル
コンパイル
コンパイルするには javacコマンドの -dオプション
を使う
javac -d 出力先ディレクトリ コンパイルしたいクラスのパス
コンパイルしたいクラスには、module-info.javaファイルも含む。
javac -d mods/freshfood \
src/freshfood/module-info.java \
src/freshfood/com/example/vegetable/*.java \
src/freshfood/com/example/fruit/*.java
コンパイル実行後のディレクトリ構成は以下のようになる。
supermarket
├── src
│ └── freshfood ← モジュール用ディレクトリ(ソース)
│ ├── module-info.java ← モジュール宣言ファイル
│ └── com/example
│ ├── vegetable ← 公開パッケージ
│ │ ├── Tomato.java
│ │ └── Carrot.java
│ └── fruit ← 非公開パッケージ
│ ├── Apple.java
│ └── Banana.java
└── mods
└── freshfood ← コンパイル出力先
├── module-info.class
└── com/example
├── vegetable
│ ├── Tomato.class
│ └── Carrot.class
└── fruit
├── Apple.class
└── Banana.class
④モジュールを実行する
モジュールを実行するには以下の2つを指定する
-
javaコマンドの --module-pathオプション
: モジュールパス(モジュールのルートディレクトリのこと) -
-mオプション
: 実行したいクラス(モジュール名とクラス名の間を/
で区切る)
java --module-path モジュールのルートディレクトリ -m モジュール名/完全修飾クラス名
Tomatoクラスを実行する例
java --module-path mods -m freshfood/com.example.vegetable.Tomato
(補足) jarファイルの利用
複数のクラスをひとつのjarファイルとしてまとめれば、実行時はモジュール名だけを指定すればよい。
モジュールの依存関係
外部のモジュールの機能を利用したい場合はmodule-info.javaに requires モジュール名
を記述する。
module freshfood {
requires モジュール名;
}
java.baseとは
java.baseとはJavaアプリケーションが動くために必須のもっとも基本的な機能を提供するモジュールのこと。すべてのjavaモジュールが自動的に依存するので、requires
で記述する必要がない。
java.baseに所属する主なクラス
- java.lang.Object
- java.lang.String
- java.util.List, java.util.Map, java.util.Set
- java.io.File, java.io.InputStream
- java.time.LocalDate, java.time.LocalTime
- java.math.BigDecimal, java.math.BigInteger
- java.net.Socket, java.net.URL
- java.util.Optional
モジュールグラフ
モジュールグラフとは、モジュール間の依存関係を表現した図のこと。
例えば、以下のようにmodule-info.javaを作成すると、
module module1 {
requires module2;
}
module module2 {
requires module3;
}
モジュールグラフはこのようになる。
その他 モジュールに関する注意事項
- モジュールの入れ子構造はできない
- モジュール名は一意に識別できるものに限る
- module-info.javaファイルは一つのモジュール内には一つだけしか定義できない