Javaのプロジェクト管理をAIに聞きながら理解する――パッケージとモジュール:AIアシスト時代のJavaプログラミング入門(12)
対話型AI(人工知能)にアドバイスを受けながら進めるJavaプログラミングの入門連載。今回は、クラスをまとめる方法としてパッケージとモジュールを学習します。基本となるパッケージと、パッケージの問題点を解決する仕組みとされるモジュールを理解しましょう。
本連載のサンプルコードをGitHubで公開しています。こちらからダウンロードしてみてください。
目次
まずはimport文の意味を解く
対話型AI(人工知能)にアドバイスを受けながら進めるJavaプログラミングの入門連載「AIアシスト時代のJavaプログラミング入門」。今回は、これまでたびたび登場しながら詳細には踏み込まなかったimport文から、パッケージとモジュールを掘り下げていきます。import文がどのような機能を背景としたものかを学習していきます。
まずは、import文の意味を知りたいということで、「Javaのimport文の意味を簡単に教えて。」という質問を、Visual Studio Code(以降、VS Code)からGitHub Copilot(以降、Copilot)に投げてみました(図1)。なお、モデルには今回もGPT-5 miniを使っていきます。
まずは「概要」と「ポイント」に注目すればよさそうです。
概要には、「クラスやメンバを簡潔に書けるようにする宣言」とあります。また、「この短い名前はこの完全修飾名を指しますと教える」ともあります。これを踏まえて、第8回のimport文とその説明を振り返ってみましょう。第8回では、import文が以下のように使われていました。
import java.util.List; import java.util.ArrayList;
この文の存在のおかげで、以下のようにArrayListを使う文を書けていました。
List<String> names = new ArrayList<>();
ここのListやArrayListが「短い名前」で、java.util.Listとjava.util.ArrayListが「完全修飾名」ということのようです。完全修飾名(Full Qualified Name)とはJavaに限った概念ではなく、階層構造になった名前をドット(.)でつなげて、対象を一意に識別するための仕組みです。
例えば、同じ「xyz」でも、aaa.bbb.xyzとccc.ddd.xyzは異なるものとするという考え方です。つまり、長い名前を短い名前で使えるようにするのがimport文の役割ということです。確かに、何度もjava.util.Listと書くよりは、Listとだけ書けた方がシンプルですね。もし、上のimport文を使わないとすれば、以下のように長い文を書かなければならないところです。これが「単一型の輸入」で書かれていることですね。
java.util.List<String> names = new java.util.ArrayList<>();
ここまでの説明で、ポイントで言わんとしていることが理解できます。「importは可読性のための記法的糖衣」とあります。上の2つの初期化文の比較で可読性に大きな差があるのは分かりますが、「記法的糖衣」とは何でしょうか? これもJavaに限った話ではないですが、「糖衣」とは「糖衣構文」というように使われ、「シンタックスシュガー」とも呼ばれます。苦い薬を甘い砂糖でくるんで飲みやすくするのと同様に、長い複雑な構文を短く楽に書けるようにするための仕組みです。しかしながら、砂糖でくるんでも薬の効用が変わらないのと同様に、糖衣構文でも元の文の意味が損なわれることはありません。これが、ポイントにある「動作や実行時の挙動は変えません」の意味です。
こう見てくると、import文の役割は非常に単純なものと理解できるでしょう。
パッケージとは
ここで図1に戻ると、気になるキーワードがたびたび登場しています。それが「パッケージ」です。抜き出してみると、以下のように使われています。
- import java.util.*;はその「パッケージ」内の全ての型を短名で使えるようにする
- java.lang「パッケージ」(例:String, System)は自動で使える
- 同じ短名の型が複数の「パッケージ」にある場合は、importしても曖昧(あいまい)になるので、java.util.Dateのように完全修飾名を使う
これを読むと、パッケージとは、「java.util」や「java.lang」のことを指すようです。ここで再び、AIにパッケージの意味を聞いてみましょう。「パッケージについて簡単に教えて。まだコードは不要です。」と投げてみました(図2)。
たくさんのことが書かれていますが、まずは「概要」と「目的」に注目しましょう。「概要」には、以下のように示されています。
・packageはクラスやインタフェースを論理的にまとめる名前空間です。ソースにpackage宣言を書き、同じパッケージ同士で関連クラスを管理します。
クラスやインタフェースを論理的にまとめる名前空間、とあります。「名前空間」もJavaに限った概念ではなく、ある名前が属する空間(スペース)を決めて、スペースが異なれば同じ名前でも別物とする、という考え方です。クラスやインタフェースを作っていくと、誰かが同じ名前のクラスを作っているかもしれません。このとき衝突を避けるために名前空間を別に作るわけです。先ほど、xyzの例えを出しましたね。
パッケージを使うには、package宣言を書くとありますが、今までpackage宣言を書いた覚えはないですし、AIのコードにもありませんでした。これがどういうことなのかは後で触れることにして、AIの回答をさらに掘り下げましょう。
パッケージの名前とフォルダ構成
「命名規則」には、パッケージにどのように名前を付けるか? ということが書かれています。
・逆ドメイン形式(例:com.example.app)が一般的で、ディレクトリ構造はパッケージ名に対応します。
逆ドメイン形式って何? ということですが、WebサイトのURL(ドメイン形式)を逆にしたものと素直に解釈すればよいでしょう。つまり、例にあるapp.example.comを逆にしたもの、ということですね。
そしてここが大事です。「ディレクトリ構造はパッケージ名に対応します」ということで、ソースファイルの場所も物理的にパッケージ名に合わせる必要があるということです。つまり、comフォルダ→exampleフォルダ→appフォルダを作り、そこにソースファイルを置く必要があるということです(ここからは、ディレクトリ=フォルダとしています)。論理的にまとめるとありながら、物理的なディレクトリ階層が絡んでくるのが面白いですね(図3)。
パッケージを宣言するコード
パッケージのイメージができてきたところで、具体的なコードに入っていきましょう。Agentモードに切り替えて、「packageという名前のプロジェクトを作り、com.example.appパッケージにMainクラスを配置して。」と投げて一連の作業を実行してもらいました(図4)。
【補足】期待する場所にプロジェクトが作成されない場合
Agentモードでプロジェクトの作成を依頼したとき、期待通りの場所(ワークスペース直下)に作成されないことがあります。これは、Windowsの場合はドライブレターが正しく解釈されないのが原因ですが、Cドライブルートにcフォルダがあり、そこからたどると目的のプロジェクトフォルダがあるはずなので、手動でワークスペースに移動してみてください。
packageプロジェクトのsrcフォルダにはcomフォルダがあり、さらにexample、appというフォルダの下にようやくMain.javaがあるのを確認できます(図5)。
ここで、作成されたMain.javaファイルを見てみましょう(図6)。
Mainクラスそのものには新しい要素はないですが、注目はpackage文です。これにより、このファイルのクラスなどはcom.example.appパッケージに属することが宣言されます。図1に戻ると、Mainの完全修飾名はcom.example.app.Mainであることが宣言されることになります。
ここで、図2の最後「注意点」にあった「無名パッケージ」について補足しておきます。前回までのサンプルでは、package宣言がありませんでした。このように、package宣言がないときは無名パッケージという名前のないパッケージとして扱われます。外部に公開しないとか、小規模だとか、名前の衝突を考慮する必要のない場合には、フォルダ階層が複雑になるのを避けるためにも無名パッケージで済ませてしまう場合があります。ただし説明にもあるように、「小規模なサンプル以外では避ける」ことが大事です。
別パッケージのクラスを利用するコード
クラスを作って、package宣言で特定のパッケージに所属させることができるのは分かりました。では、別のパッケージから利用するにはどのようにすればよいでしょうか。「packageプロジェクトに、com.example.another-appパッケージとAnotherクラスを追加し、Mainクラスから呼び出すコードを記述して。」と投げてみました(図7)。
packageプロジェクトのsrcフォルダ以下を見ていくと、exampleフォルダの下にanotherappというフォルダの下にAnother.javaがあるのを確認できます(図8)。
【補足】パッケージ名にハイフンは使用できない
ここで、com.example.another-appパッケージの作成を依頼しましたが、実際に作成されたのはcom.example.anotherappパッケージでした。このように、Javaではパッケージ名にハイフン(-)は使えません。AIはハイフンを除去してくれましたが、このように英小文字(と数字)のみで命名します。アンダースコア(_)も使えますが、一般的なJavaコーディング規約では使用が禁止されています。
追加されたAnother.javaファイルを見てみましょう(図9)。
先ほどのMain.javaを見ていれば、特筆すべき点はありませんね。続けて、改変されたはずのMain.javaファイルを見てみましょう(図10)。
ここで注目すべきは(1)のimport文です。既述の通り、import文で別のパッケージにあるAnotherを、(2)のように短い名前で使えるようにします。もし(1)がなければ、(2)はcom.example.anotherapp.Another.greet()というように長い名前でメソッドを呼び出さねばなりませんね。
可視性とパッケージプライベート
さらに、図2に戻って、気になる説明を読んでみます。「可視性」です。
・修飾子なし(デフォルト)は「パッケージプライベート」で、同一パッケージからのみアクセス可能。publicはどこからでも参照可。
図9を振り返ると、Anotherクラスの宣言にはpublicが付いています。publicについては第6回でカプセル化とともに触れましたが、クラス定義に使用すると他のパッケージから可視であるかどうかを意味します。publicと指定したことで、別パッケージのMainクラスから参照できたわけです。試しにpublicを外してみるとコンパイルできないはずです。逆を言うと、publicを付与されていないクラスは、パッケージ内でしか使用できない「パッケージプライベート」になるということです。
こんな質問をしてみたい!
ここまで来ると、図1の「ワイルドカード」「自動インポート」「名前衝突」についても、何を言っているのか理解できるのではないでしょうか。ただ、「静的インポート」については意味がよく分からないかもしれません。静的インポートについて聞いてみると、これまで取り上げてこなかった静的メンバーのことが理解できるかもしれませんよ。
モジュールとは
ここまでパッケージをAIに聞きながら学習してきました。このように、パッケージは非常に便利なものだというのをお伝えできたと思います。ここでは続けて、パッケージに関連した機能としてモジュールを紹介します。
パッケージの問題点
モジュールは、パッケージの問題点を補うためにJavaバージョン9で登場しました。ここまで本記事を読んできた読者には、可視性がpablicとパッケージプライベートしかないとか、パッケージの階層と物理的なフォルダ階層を一致させる必要があるとか、使いにくい面もありそうなことに気付いた人もいるのではないでしょうか。モジュールを紹介するに当たり、まずはこのようなパッケージ問題点を整理してみます。「パッケージの問題点を簡単に教えて。」と投げてみました(図12)。
やや難しい表現もありますが、重要そうなものを3つほどピックアップして補足します。
(1)可視性の制約
前述の通り、クラス定義にpublicを付与しないとパッケージプライベートとなり、外部から見えなくなります。よって、別パッケージからのアクセス(テストなど)がやりにくくなり、不便です。とはいえ全部publicにしたら、いらぬものまで公開してしまうという、両極端な可視性といえます。
【補足】テストと可視性
ここで言う「テスト」とは、メソッドなどが正しく動作するかどうか確認する作業です。テスト用のコードを実行して対象メソッドをテストします。テストのコードは別パッケージとして独立させておくのが常なので、対象のメソッドにアクセスできないとテストできません。
(2)部分的な階層誤解
少し意味が分かりにくい表現ですが、図2の「サブパッケージ」に書かれているように、見た目に親子関係にあるパッケージでも、お互いに無条件に可視となることはありません。別パッケージとして、意識的にクラス定義にpublicを付与しないと可視になりません。これが可視性の設定の際に誤解を招くといった意味と思われます。
(3)リファクタのコスト
リファクタとは、クラス名やメソッド名といった識別子をプロジェクト内やパッケージ内で変更することをいいます。変更は宣言した箇所だけではなく、使用している全ての箇所で必要です。前述の通りパッケージには物理的なフォルダ階層も絡むので、パッケージの変更はフォルダ間での移動も伴いますし、利用する側ではimport文の修正も必要となります。これはかなりの手間といえますね。
これらの問題点を解決する一つの手段がモジュールです。これを確認するために、「パッケージの問題点を解決する機能があれば代表的なものを教えて。」と投げてみました(図13)。
たくさんの機能が示されましたが、最初に「Javaモジュールシステム」が提示されました。やや長い名前になっていますが、意味は同じでモジュールです(JPMSは、Java Platform Module Systemの略です)。さらに続けて、module-info.javaとも示されています。1行説明には、以下のようにあります。
・exports/requires/opensにより可視性と依存を明示して、意図しないアクセスや依存の拡散を防ぐ。スプリットパッケージを検出・防止できる。
module-info.javaとか、exports/requires/opensとか、スプリットパッケージとかいろいろ登場しますが、これがモジュールを構成するファイル名とか、構文のようです。具体的な説明を聞く前に、実際にモジュールをプロジェクトに導入して、どのようにモジュールを利用するのか見てみましょう。
モジュールの導入
ここまで使ってきたpackageプロジェクトに、モジュールを導入してもらいます。「packageプロジェクトにモジュールを導入して。」と投げてみました(図14)。
図14を見ると、「次に module-info.java を追加します(モジュール名: com.example.appmodule)。」とあります。既述の通り、module-info.javaファイルがモジュール化において重要な役割を果たすということが分かります。packageプロジェクトを見ると、comフォルダと同じ階層にmodule-info.javaがあるのを確認できます(図15)。
module-info.javaの具体的な役割は何でしょうか。そこで、生成されたmodule-info.javaについて説明してもらいましょう。module-info.javaをコンテキストに指定して、「module-info.javaについて簡単に説明して。」と投げてみました(図16)。
module-info.javaについての一般的な話と、今回作成されたファイルのそれぞれについて言及されています。「概要」には、module-info.javaはモジュールの宣言ファイルで、モジュール名、公開パッケージ、依存関係を宣言するとあります。これ以外の回答も踏まえて、作成されたmodule-info.javaについて説明してみたのが図17です。
module文でモジュール名を宣言し、export文で外部に公開するパッケージを指定するということですね。図16の「効果」にあるように、
exportsしたパッケージのみ他モジュールからコンパイル・実行時に参照可能(publicと併用しても、モジュールがエクスポートしていなければ他モジュールから見えません)
となるようです。つまり、パッケージでクラスをpublic宣言しても、ここでexportsされていない限りは見えないということですね。これによって、問題点の(1)である不可視問題が解決されそうです。
生成されたmodule-info.javaには依存関係の例がなかったので、回答の最後にもあるように、これを追加してもらいましょう。「requiresを適当に追加して。」と投げてみました(図18)。
改めてmodule-info.javaを見てみると、図で示されたように以下の3行が追加されています。それぞれ、java.logging、java.sql、java.xmlモジュールへの依存関係があることを示しています。
requires java.logging; requires java.sql; requires java.xml;
パッケージだけでは、別のパッケージを使っているかどうかはソースコードをつぶさにチェックしないと分かりませんが、module-info.javaに依存関係を記述しておくことで、「どのようなモジュールが必要か」が明確になります。また、依存関係に指定したモジュールが見つからなければただちにエラーとすることができます。
モジュールによって、公開したいパッケージと依存するモジュールを明確に指定できることで、パッケージだけではできなかった可視性やアクセス制御が可能になったといえるでしょう。
こんな質問をしてみたい!
ここでは、モジュールのごくごく基本的な部分に絞って学習しました。そのため、パッケージの問題点(2)と(3)については未解決に見えます。この解決方法を聞いてみると、ここでは触れられなかったmodule-info.javaの記述内容についての理解が、より深まるかもしれませんよ!
まとめ
今回は、クラスをまとめる方法としてパッケージとモジュールを、AIに聞きながら学習しました。
最終回となる次回は、ラムダ式とStream APIを学習します。
筆者紹介
WINGSプロジェクト 山内直
WINGSプロジェクト所属のテクニカルライター。出版社秀和システムを経てフリーランスとして独立。ライター、編集者、開発者、講師業に従事。屋号は「たまデジ。」。
・たまデジ。 | たまプラーザで生活、仕事する。(https://naosan.jp/)
WINGSプロジェクト
有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティー(代表山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手掛ける。2021年10月時点での登録メンバーは55人で、現在も執筆メンバーを募集中。興味のある方は、どしどし応募頂きたい。著書、記事多数。
・サーバーサイド技術の学び舎 - WINGS(https://wings.msn.to/)
・RSS(https://wings.msn.to/contents/rss.php)
・X: @WingsPro_info(https://x.com/WingsPro_info)
・Facebook(https://www.facebook.com/WINGSProject)
Copyright © ITmedia, Inc. All Rights Reserved.
関連記事
プログラマー以外にもおすすめ 「Visual Studio Code」のインストールから基本設定まで
プログラミング用としては、利用者が多いコードエディタの「Visual Studio Code」。拡張機能が豊富で、エンドユーザーや管理者であっても、高機能なテキストエディタとして活用できるものとなっている。特に最近では、ツール類の設定ファイルがJSON形式やXML形式になっている。これらを編集する際にも、Visual Studio Codeは便利だ。
Visual Studio Codeを活用するための人気TIPS 12選
人気過去連載を電子書籍化して無料ダウンロード提供する@IT eBookシリーズ。第52弾は、Microsoftが無償で提供するソースコードエディタ「Visual Studio Code」。高機能なこのエディタを使いやすくするTIPS集から12本をまとめてお送りする。
初心者向け、データ分析・AI・機械学習の勉強方法 Deep Insiderで学ぼう
データ分析、AI/機械学習の実装、生成AIの活用(まとめてデータサイエンス)は、もはや多くの人に必要な知識となっています。これらの基礎はどうやって学べばよいのでしょうか? オススメの勉強方法を紹介します。


















