Java入門|パッケージを階層に分ける

パッケージを階層で整理すると、クラスの役割が見えやすくなり、大きなプログラムもすっきり管理できる

Javaでプログラムを作っていると、最初のうちはクラスの数も少ないので、1つか2つのパッケージだけでも十分に整理できます。ですが、学習が進んでプログラムの規模が大きくなってくると、クラスの数が増え、役割も細かく分かれていきます。そうなると、同じパッケージの中に何でも入れてしまう方法では、少しずつ見通しが悪くなってきます。

そんなときに役立つのが、パッケージを階層に分けるという考え方です。Javaでは、パッケージの中にさらに下位のパッケージを作ることができます。これを使うと、似た役割のクラスをグループごとに細かく整理できるので、プログラム全体の構造がとても見やすくなります。

今回は、このしくみをサブパッケージという考え方とあわせて見ていきます。ドラゴンボールの世界をイメージしたクラスを使いながら、フォルダ構成、package文の書き方、コンパイル方法、実行方法まで、順番にわかりやすく確認していきましょう。

パッケージを階層に分けるとは

Javaのパッケージは、1段だけの分類ではありません。
必要に応じて、さらに下の階層を作れます。

たとえば、pa というパッケージがあるとします。
その下に sub という分類を作ると、パッケージ名は pa.sub になります。

このように、ピリオドでつないで表すことで、Javaではパッケージの階層を表現できます。

たとえばイメージとしては、こんな感じです。

書き方意味
pa大きな分類
pa.subpa の中をさらに細かく分けた分類

つまり、パッケージを階層に分けるというのは、クラスをより細かく整理するための方法です。

サブパッケージとは何か

パッケージの下の階層に作るパッケージを、サブパッケージと呼びます。
つまり、pa.sub の sub の部分がサブパッケージです。

この仕組みを使うと、たとえば次のように役割で分けられます。

パッケージ入れるクラスの例
pa基本となる共通クラス
pa.sub補助的なクラス、細かい機能のクラス
pb実行用クラスや別の機能グループ
pcさらに別の機能グループ

クラスが増えてくると、こうした整理はとても大切になります。
どのクラスがどの機能に属しているのかが、名前を見るだけでもわかりやすくなるからです。

なぜ階層化が必要なのか

小さなプログラムでは、パッケージが1つでも十分なことがあります。
でも、機能が増えてくると、同じパッケージの中にたくさんのクラスが集まりすぎてしまいます。

たとえば、ドラゴンボール風のプログラムを考えると、こんなクラスが出てきそうです。

  • 戦士を表すクラス
  • 技を表すクラス
  • 修行を表すクラス
  • バトルを表すクラス
  • 補助処理を行うクラス
  • 実行を開始するクラス

これらを全部1つのパッケージに入れると、あとから見返したときにかなり探しづらくなります。
そこで、似た役割のものをサブパッケージに分けていくと、整理しやすくなります。

状態階層化しない場合階層化した場合
クラス数が増えたときごちゃごちゃしやすい役割ごとに整理しやすい
修正するとき探すのに時間がかかる関係する場所を見つけやすい
複数人で開発するとき担当範囲が見えにくい機能ごとに分担しやすい

このように、サブパッケージは大きなプログラムを見やすくするための大切な工夫です。

package文の書き方

サブパッケージを使うときは、package文に階層をそのまま書きます。

package pa.sub;

この書き方によって、そのファイルのクラスは pa.sub パッケージに属することになります。

ここで大事なのは、pa と pa.sub は別のパッケージとして扱われるということです。
名前がつながっているので、何となく同じグループに見えますが、Javaのコード上では独立した別のパッケージです。

フォルダ構成との対応

パッケージを階層に分けたときは、フォルダ構成もそれに対応させます。
package pa.sub; と書くなら、フォルダも pa の下に sub を作ります。

今回の例では、作業中ディレクトリの構成は次のようになります。

つまり、

  • pa はフォルダ pa
  • sub は pa の下のフォルダ sub

に対応しています。

この対応関係がずれてしまうと、Javaはクラスの位置を正しく認識できません。
そのため、package文とフォルダ構成は必ずそろえる必要があります。

サンプルプログラムで見てみよう

クラス名はドラゴンボールをイメージして Fighter にし、情報設定用のメソッドは setFighter にしています。

ファイル名:Sample7.java

package pa.sub;

// 戦士クラス
class Fighter {
    private String name;
    private int power;

    public Fighter() {
        name = "未設定";
        power = 0;
        System.out.println("戦士を準備しました。");
    }

    public void setFighter(String n, int p) {
        name = n;
        power = p;
        System.out.println("戦士名を" + name + "に、戦闘力を" + power + "にしました。");
    }

    public void show() {
        System.out.println("戦士名は" + name + "です。");
        System.out.println("戦闘力は" + power + "です。");
    }
}

class Sample7 {
    public static void main(String[] args) {
        Fighter fighter1 = new Fighter();
        fighter1.show();
    }
}

このプログラムのポイント

このサンプルで注目したいのは、先頭の

package pa.sub;

という部分です。
この1行によって、FighterクラスとSample7クラスは、どちらも pa.sub パッケージに含まれます。

今回の役割を整理すると、次のようになります。

クラス名役割
Fighter戦士名と戦闘力を管理する
Sample7mainメソッドを持ち、処理を開始する

同じ pa.sub パッケージに属しているので、Sample7 から Fighter をそのまま使えます。

実行結果

このプログラムを実行すると、たとえば次のように表示されます。

戦士を準備しました。
戦士名は未設定です。
戦闘力は0です。

コンストラクタで初期状態を作り、そのあと show メソッドで内容を表示している流れです。
とてもシンプルですが、サブパッケージの使い方を確認するにはちょうどよい例です。

コンパイルの方法

サブパッケージを使う場合も、作業中ディレクトリからコンパイルします。
今回なら、Sample7.java は pa\sub の中にあるので、次のように入力します。

PS C:\Java\13>javac pa\sub\Sample7.java

指定するパスが少し長くなっていますね。
これは、パッケージが階層化されているぶん、フォルダ構成も深くなっているからです。

実行の方法

実行するときは、mainメソッドを持つ Sample7 クラスを、サブパッケージまで含めて指定します。

PS C:\Java\13>java pa.sub.Sample7

ここで pa.Sample7 としてはいけません。
正しくは pa.sub.Sample7 です。

サブパッケージまで含めた完全な名前で指定することが大切です。

これまでのパッケージとの違い

使い方の流れそのものは、これまでのパッケージと大きくは変わりません。
違うのは、階層が1段増えていることです。

項目通常のパッケージサブパッケージ
package文package pa;package pa.sub;
保存先pa フォルダpa\sub フォルダ
実行時の指定java pa.SampleXjava pa.sub.Sample7

つまり、考え方は同じで、指定する名前が少し長くなるだけです。
その代わり、クラスの整理はぐっとしやすくなります。

pa と pa.sub は別パッケージ

ここはとても大事なので、しっかり押さえておきたいポイントです。

pa と pa.sub は、名前が似ていても別々のパッケージです。

たとえば、

  • pa にある Fighter クラス
  • pa.sub にある Fighter クラス

は、同じ Fighter という名前でも別のクラスとして扱われます。

表にするとこうなります。

記述意味
pa.Fighterpa パッケージの Fighter クラス
pa.sub.Fighterpa.sub パッケージの Fighter クラス

このように、サブパッケージは上位パッケージの一部のように見えても、Javaでは独立した名前空間です。

サブパッケージを使うメリット

サブパッケージを使うと、プログラム全体の整理がかなりしやすくなります。

メリット内容
機能ごとに整理しやすい似た役割のクラスをまとめられる
見通しがよくなるどこに何があるか把握しやすい
クラス数が増えても管理しやすい大規模開発に向いている
名前の衝突を避けやすい同名クラスでも区別できる

たとえば、ドラゴンボール風のプログラムなら、こんな分け方もできます。

  • pa.character
  • pa.battle
  • pa.training
  • pa.item

こうしておくと、役割ごとにクラスが整理されるので、とてもわかりやすくなります。

図で整理すると理解しやすい

この図では、左側にフォルダ構成、右側にパッケージ構成を配置します。
左側では、13 フォルダの下に pa、その下に sub があり、その中に Sample7.java が保存されている様子を表します。

右側では、それに対応する pa.sub パッケージの中に、FighterクラスとSample7クラスが入っている様子を示します。
この2つを対応させて見ることで、package文とフォルダ構成の関係がつかみやすくなります。

つまずきやすいポイント

サブパッケージでは、次のようなところで混乱しやすいです。

つまずきやすい点気をつけたいこと
pa.sub は pa の一部だから同じ扱いだと思うJavaでは別パッケージとして扱う
フォルダを pa だけ作ってしまうpa の下に sub も必要
実行時に pa.Sample7 と書いてしまう正しくは pa.sub.Sample7
package文と保存場所がずれるpackage pa.sub; なら pa\sub に保存する

このあたりを意識しておくと、かなりスムーズに理解できます。

修飾子やアクセスの考え方にもつながる

サブパッケージを理解すると、あとで学ぶアクセス修飾子の意味も見えやすくなります。
たとえば、同じパッケージだけで使えるのか、別パッケージからも使えるのか、といった話を考えるときに、pa と pa.sub が別パッケージだとわかっていることがとても重要です。

つまり、サブパッケージはただの見た目の分類ではなく、Javaのコードのまとまりをきちんと理解するための土台にもなります。

ここで押さえたいこと

今回の内容で、特に大切なのは次の点です。

大事なポイント内容
パッケージは階層化できるピリオドでつないで表す
サブパッケージは機能ごとの整理に便利クラス数が増えても見やすい
package文とフォルダ構成は対応するpa.sub なら pa\sub
実行時はサブパッケージも含めて指定するjava pa.sub.Sample7
pa と pa.sub は別のパッケージ名前が似ていても同一ではない

パッケージを階層に分ける考え方は、Javaで少し大きめのプログラムを扱うときにとても重要です。
クラスを役割ごとにきれいに整理できるようになると、コードを読むのも直すのもずっと楽になります。

ここまで理解できると、Javaのパッケージ機能をかなり実践的に使えるようになってきます。