CodeGym /コース /JAVA 25 SELF /module-info.java: 構文とモジュールの作成

module-info.java: 構文とモジュールの作成

JAVA 25 SELF
レベル 60 , レッスン 1
使用可能

1. module-info.java とは何か、どこにあるのか?

Java のモジュールは常にファイル module-info.java から始まります。これはいわばモジュールの「パスポート」で、モジュール名、エクスポートするパッケージ、他モジュールへの依存関係を宣言します。

どこにある?
ファイル module-info.java は、モジュールのソース・ルート直下(ソースフォルダのルート)に置きます。例えば、プロジェクト構成が次のような場合:

project-root/
└── src/
    └── my.module.name/
        ├── module-info.java
        └── com/
            └── example/
                └── api/
                    └── MyClass.java

ここで my.module.name はモジュール名です(命名規則は後述)。

このファイルがないと、モジュールはモジュールとは見なされません!
もし存在しなければ、たとえフォルダやクラスが大量にあっても、ただの従来型の Java プロジェクトということになります。

2. module-info.java の構文: 基本要素

まずは最もシンプルなファイル例を見てみましょう — 怖くありません、ほんとです!

module my.module.name {
    exports com.example.api;
    requires java.sql;
}

部分ごとに見ていきましょう:

module my.module.name { ... }
これは my.module.name という名前のモジュールの宣言です。モジュール名は一意の識別子で、一般的にはルートパッケージ名と一致させます(例: com.example.app)。
豆知識: モジュール名を java.base にするとコンパイラは機嫌を損ねます。標準モジュールを置き換えようとするのはやめましょう。

exports com.example.api;
この行は「私は、com.example.api パッケージ内のものを公開します」と示しています。このパッケージ内で public として宣言されたものは、他モジュールから見えるようになります。それ以外はモジュール内限定です。

requires java.sql;
ここではコンパイラに対して正直に「このモジュールは標準モジュール java.sql を必要とします」と宣言しています。これを宣言しないと、そのモジュールのクラスは使えません。

追加のキーワード(知っておくと便利)

  • opens <package>; — リフレクション用にパッケージを開きます(例: Jackson のようなシリアライゼーション系ライブラリ)。
  • uses <service-interface>; — モジュールがあるサービス(インターフェース)を利用することを示します。
  • provides <service-interface> with <implementation-class>; — モジュールがそのサービスの実装を提供することを示します。

本講義では exportsrequires に焦点を当てます。これらは学習用・本番の大半のプロジェクトで必要になります。

3. module-info.java の例

例 1. 最小のモジュール

module com.example.hello {
    exports com.example.hello.api;
}
  • このモジュールはパッケージ com.example.hello.api のみをエクスポートします。
  • com.example.hello.internal のような場所にあるものは、たとえ public クラスがあっても他モジュールからは見えません。

例 2. 依存関係を持つモジュール

module com.example.dbclient {
    exports com.example.db.api;
    requires java.sql;
}

JDBC を使えるのは、java.sql への依存関係を正しく宣言しているからです。

例 3. 複数のエクスポート対象パッケージ

module com.example.library {
    exports com.example.library.api;
    exports com.example.library.utils;
}

エクスポートするパッケージはいくつでも構いません(ただし何でもかんでもエクスポートするのはやめましょう。モジュール化の意義が失われます!)。

4. 制約とルール

モジュール名

  • 一般的にルートパッケージ名と一致させます(例: com.example.app)。
  • 標準モジュール名(java.basejava.sql など)と同じにしてはいけません。
  • 空白や特殊文字を含めない、数字で始めない、などの制約があります。
  • 推奨: 衝突を避けるため、組織やプロジェクトの逆ドメイン名を使いましょう。

1 モジュールにつき 1 つの module-info.java
1 つのモジュールにこのファイルは 1 つだけです。2 つあるとコンパイラが「モジュール騒動」を起こします。

同一パッケージのエクスポートは 1 つのモジュールからのみ
同じパッケージを 2 つの異なるモジュールからエクスポートすることはできません。1 つの名前にパスポートが 2 つあるようなものです — 国は認めません。

モジュール内のパッケージ
実際にそのモジュールのソース構成内に存在するパッケージだけをエクスポートできます。存在しないパッケージをエクスポートしようとするとコンパイルエラーになります。

5. 実践: プロジェクトで module-info.java を作成する

次の内容のシンプルなプロジェクトがあるとします:

project-root/
└── src/
    └── com.example.greetings/
        ├── module-info.java
        └── com/
            └── example/
                └── greetings/
                    ├── api/
                    │   └── Greeter.java
                    └── internal/
                        └── SecretSauce.java

ステップ 1. module-info.java を作成する

module com.example.greetings {
    exports com.example.greetings.api;
}

ステップ 2. 別モジュールで internal パッケージのクラスを使ってみる

2 つ目のモジュール com.example.appSecretSauce にアクセスしたいとします:

module com.example.app {
    requires com.example.greetings;
}

import com.example.greetings.internal.SecretSauce; // エラー!

結果:
コンパイラは「パッケージ com.example.greetings.internal はモジュール com.example.greetings によってエクスポートされていません」と言うでしょう。たとえクラス SecretSaucepublic でも、他モジュールからはアクセスできません。
これがモジュールレベルの本当のカプセル化です。

ステップ 3. requires を宣言しない場合

もし com.example.apprequires com.example.greetings; を書かずに、com.example.greetings.api のクラスを使おうとすると、コンパイラは次のエラーを出します:

package com.example.greetings.api is not visible

6. module-info.java を扱うときのよくあるミス

エラー 1: モジュール名とプロジェクト構成の不一致。
モジュールを com.example.app と名付けたのに、フォルダ構成が src/main/java/app のようになっていると、コンパイラは意図を理解できません。モジュール名は通常ルートパッケージと一致し、フォルダもそれを反映している必要があります。

エラー 2: 片っ端からエクスポートする。
他モジュールから見える必要があるものだけをエクスポートしましょう。「その方が簡単だから」といって exports com.example; のように大ざっぱに書くのはやめましょう。カプセル化が壊れます。

エラー 3: requires の書き忘れ。
他モジュールや標準ライブラリ(例: java.sql)のクラスを使うのに、依存関係を宣言し忘れるとコンパイルエラーになります。

エラー 4: exports に存在しないパッケージ。
exports com.example.foo; と書いたのに、そのパッケージが存在しない場合 — コンパイラは「空気をエクスポートしています」と言うでしょう。

エラー 5: 非エクスポートパッケージ内の public クラス。
クラスが public であっても、そのクラスが属するパッケージをエクスポートしていなければ、そのクラスはモジュール内でしか見えません。エラーではありませんが、初心者が驚きがちなポイントです。

エラー 6: 同一モジュール内に複数の module-info.java
1 つのモジュールには module-info.java は 1 つだけです。2 つあるとコンパイラはプロジェクトをビルドできません。

コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION