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

パッケージを階層で整理すれば、クラスの住所がもっと分かりやすくなる。
サブパッケージを理解すると、大きなJavaプログラムでも、役割ごとにすっきり管理できるようになります。

Javaでプログラムを作っていると、最初のうちはクラスの数が少ないため、1つか2つのパッケージだけでも十分に整理できます。

たとえば、pa パッケージに学習用のクラスをまとめたり、pb パッケージに実行用のクラスを置いたりするだけでも、基本的な構成は理解しやすくなります。

しかし、学習が進んでプログラムの規模が大きくなると、クラスの数が増え、役割も細かく分かれていきます。

鬼滅の刃風にたとえると、最初は鬼殺隊士の名簿だけを管理していればよかったものが、だんだん次のような巻物が増えていくイメージです。

巻物の種類Javaでのクラスのイメージ
鬼殺隊士の名簿DemonSlayer クラス
呼吸の型の記録BreathingStyle クラス
任務の指令書Mission クラス
日輪刀の台帳NichirinSword クラス
戦闘記録BattleManager クラス
補助処理の巻物SupportTool クラス

これらをすべて1つのパッケージに入れてしまうと、どこに何があるのか探しにくくなります。

そこで役立つのが、パッケージを階層に分ける という考え方です。

Javaでは、パッケージの中にさらに細かい分類を作れます。
たとえば、pa という大きなパッケージの下に sub という分類を作ると、パッケージ名は次のようになります。

package pa.sub;

これは、pa の中をさらに sub という単位で細かく整理している形です。

鬼滅の刃風に言えば、pa という鬼殺隊本部の大きな棚の中に、sub という補助巻物専用の小さな棚を作るようなものです。

このように階層化すると、クラスの役割が見えやすくなり、大きなプログラムでも整理しやすくなります。

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

Javaのパッケージは、1段だけの分類ではありません。

必要に応じて、パッケージ名をピリオドでつなぎ、階層構造として表せます。

たとえば、次のような形です。

パッケージ名意味
pa大きな分類
pa.subpa の下にある sub という細かい分類

pa.sub と書くことで、pa の中をさらに sub で分類していることを表します。

ただし、ここで大切なのは、pa と pa.sub は別々のパッケージとして扱われる という点です。

名前だけ見ると、pa.sub は pa の一部のように感じます。
しかし、Javaのコード上では、pa と pa.sub は独立した別のパッケージです。

書き方Javaでの扱い
papa パッケージ
pa.subpa.sub パッケージ
pa.Fighterpa パッケージの Fighter クラス
pa.sub.Fighterpa.sub パッケージの Fighter クラス

鬼滅の刃風にたとえると、pa は本部の大きな棚です。
pa.sub は、その中にある補助任務専用の棚です。

見た目には親子関係があるように見えますが、Javaのパッケージとしては、それぞれ別の棚として扱います。

サブパッケージとは何か

パッケージの下の階層に作るパッケージを、サブパッケージ と呼びます。

たとえば、pa.sub の場合、sub が pa の下にあるサブパッケージです。

用語意味
パッケージpa大きな分類
サブパッケージpa.subpa の下にある細かい分類
ピリオド.階層を表す区切り

鬼滅の刃風にたとえると、鬼殺隊本部の棚をさらに細かく分けるようなものです。

Javaのパッケージ鬼滅の刃風のイメージ
pa鬼殺隊本部の基本棚
pa.subpa の中にある補助任務用の小棚
pa.character隊士情報用の小棚
pa.battle戦闘処理用の小棚
pa.training修行記録用の小棚

サブパッケージを使うと、似た役割のクラスをさらに細かく整理できます。

クラスが少ないうちは、pa だけでも十分です。
しかし、クラスが増えてくると、pa の中にすべてを入れるよりも、pa.sub や pa.battle のように分けたほうが見通しがよくなります。

なぜパッケージを階層化するのか

小さなプログラムなら、1つのパッケージにすべてのクラスを入れても、それほど困らないことがあります。

しかし、プログラムが大きくなると、クラスの数が増え、役割も細かくなります。

鬼滅の刃風の学習プログラムを考えると、次のようなクラスが出てきそうです。

クラス役割
DemonSlayer鬼殺隊士を表す
BreathingStyle呼吸の型を表す
Mission任務を表す
BattleManager戦闘の進行を管理する
TrainingMenu修行内容を管理する
SupportTool補助処理を行う

これらをすべて同じ pa パッケージに入れると、クラスが増えたときに探しにくくなります。

そこで、役割ごとにサブパッケージへ分けます。

パッケージ入れるクラスの例
pa.character鬼殺隊士、柱、支援隊士など
pa.battle戦闘処理、技の処理など
pa.training修行メニュー、訓練記録など
pa.item日輪刀、防具、道具など
pa.sub補助的なクラス、細かい機能のクラス

このように分けると、どこに何があるのかが見えやすくなります。

状況階層化しない場合階層化した場合
クラス数が増えたとき1つの場所に集まりすぎる役割ごとに整理できる
修正するとき探すのに時間がかかる関係する場所を見つけやすい
複数人で開発するとき担当範囲が分かりにくい機能ごとに分担しやすい
同名クラスがあるとき衝突しやすいパッケージ名で区別できる

パッケージの階層化は、大きなプログラムを見やすく保つための大切な工夫です。

package文の書き方

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

たとえば、pa の下に sub というサブパッケージを作る場合は、次のように書きます。

package pa.sub;

この1行によって、そのファイル内のクラスは pa.sub パッケージに属します。

package 文所属するパッケージ
package pa;pa パッケージ
package pa.sub;pa.sub パッケージ
package pa.battle;pa.battle パッケージ

ここで大切なのは、package 文はソースファイルの先頭に書くということです。

package pa.sub;

class Sample7
{
}

package 文の前にクラス定義を書くことはできません。
また、クラス定義の途中に package 文を書くこともできません。

鬼滅の刃風にたとえると、巻物の一番上に「この巻物は pa.sub の棚に置く」と札を付けるようなものです。

package文とフォルダ構成は対応する

パッケージを階層に分けた場合、フォルダ構成もそれに対応させます。

package pa.sub; と書いた場合、ファイルは pa フォルダの下にある sub フォルダに保存します。

作業中フォルダを C:\Java\13 とすると、構成は次のようになります。

C:\Java\13
        └─ pa
           └─ sub
              └─ Sample7.java
package 文保存場所
package pa;pa フォルダ
package pa.sub;pa\sub フォルダ

pa.sub というパッケージ名は、フォルダ構成では pa\sub に対応します。

パッケージ名の部分フォルダ
papa フォルダ
subpa の下の sub フォルダ

この対応がずれると、Javaがクラスの場所を正しく見つけにくくなります。

鬼滅の刃風に言えば、巻物の札には pa.sub と書いてあるのに、実際には pa の棚だけに置いてしまうようなものです。
sub の棚まで用意して、そこに置く必要があります。

↓クリックすると拡大表示されます。

今回のサンプルで使う構成

今回は、pa.sub パッケージの中に Sample7.java を置きます。

Sample7.java の中には、鬼滅の刃風にアレンジした次の2つのクラスを書きます。

クラス名役割
DemonSlayer鬼殺隊士の名前と階級を管理する
Sample7main メソッドを持ち、処理を開始する

DemonSlayer は、鬼殺隊士の情報を表すクラスです。

DemonSlayer の要素内容
name鬼殺隊士の名前
rank階級
setSlayer()名前と階級を設定する
show()現在の情報を表示する

Sample7 は、DemonSlayer オブジェクトを作成して、show() を呼び出す実行用のクラスです。

Sample7 の役割内容
main メソッドを持つプログラムの開始地点
DemonSlayer を作る鬼殺隊士オブジェクトを作成する
show() を呼ぶ鬼殺隊士の情報を表示する

どちらのクラスも、同じ Sample7.java の中にあり、先頭に package pa.sub; があるため、pa.sub パッケージに属します。

パッケージの階層を確認する

ファイル名:Sample7.java

package pa.sub;

// 鬼殺隊士クラス
class DemonSlayer
{
    private String name;
    private String rank;

    public DemonSlayer()
    {
        name = "未登録";
        rank = "階級未設定";
        System.out.println("鬼殺隊士を準備しました。");
    }

    public void setSlayer(String n, String r)
    {
        name = n;
        rank = r;
        System.out.println("隊士名を" + name + "、階級を" + rank + "にしました。");
    }

    public void show()
    {
        System.out.println("隊士名は" + name + "です。");
        System.out.println("階級は" + rank + "です。");
    }
}

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

実行結果

鬼殺隊士を準備しました。
隊士名は未登録です。
階級は階級未設定です。

このプログラムでは、DemonSlayer オブジェクトを作成し、show() を呼び出しています。

コンストラクタでは、name に 未登録、rank に 階級未設定 を設定しています。

public DemonSlayer()
{
    name = "未登録";
    rank = "階級未設定";
    System.out.println("鬼殺隊士を準備しました。");
}

そのため、show() を呼び出すと、初期状態の隊士情報が表示されます。

public void show()
{
    System.out.println("隊士名は" + name + "です。");
    System.out.println("階級は" + rank + "です。");
}

package pa.sub; が表していること

Sample7.java の先頭には、次の1行があります。

package pa.sub;

この1行によって、Sample7.java に書かれている DemonSlayer クラスと Sample7 クラスは、どちらも pa.sub パッケージに属します。

クラス所属パッケージ役割
DemonSlayerpa.sub鬼殺隊士の情報を管理する
Sample7pa.submain メソッドで処理を開始する

同じ pa.sub パッケージ内にあるため、Sample7 から DemonSlayer をそのまま使えます。

DemonSlayer slayer1 = new DemonSlayer();

ここでは、pa.sub.DemonSlayer と書いていません。
同じパッケージ内にあるため、クラス名だけで自然に使えます。

鬼滅の刃風にたとえると、Sample7 の任務指令書と DemonSlayer の隊士名簿は、どちらも pa.sub の棚にあるため、同じ棚の巻物として扱いやすいということです。

コンパイルの方法

サブパッケージを使う場合も、コンパイルは作業中フォルダから行います。

今回のフォルダ構成は次のとおりです。

C:\Java\13
        └─ pa
           └─ sub
              └─ Sample7.java

作業中フォルダが C:\Java\13 の場合、次のように入力します。

PS C:\Java\13>javac pa\sub\Sample7.java
コマンド意味
javac pa\sub\Sample7.javapa\sub フォルダ内の Sample7.java をコンパイルする

パッケージが pa.sub なので、ファイルの場所は pa\sub\Sample7.java になります。

コンパイルが成功すると、Sample7.java の中にあるクラスに対応して、class ファイルが作成されます。

C:\Java\13
        └─ pa
           └─ sub
              ├─ Sample7.java
              ├─ DemonSlayer.class
              └─ Sample7.class
ソース内のクラス作成される class ファイル
DemonSlayerDemonSlayer.class
Sample7Sample7.class

1つのソースファイルの中に複数のクラスがある場合、コンパイル後はクラスごとに .class ファイルが作成されます。

↓クリックすると拡大表示されます。

実行の方法

実行するときは、main メソッドを持つクラスを指定します。

今回、main メソッドを持っているのは Sample7 クラスです。

ただし、Sample7 は pa.sub パッケージに属しています。

そのため、実行時にはサブパッケージまで含めて指定します。

PS C:\Java\13>java pa.sub.Sample7
実行コマンド意味
java pa.sub.Sample7pa.sub パッケージの Sample7 クラスを実行する

ここで java pa.Sample7 と書いてはいけません。

Sample7 は pa パッケージではなく、pa.sub パッケージに属しているからです。

間違いやすい実行正しい実行
java Sample7パッケージ名がない
java pa.Sample7sub が抜けている
java pa.sub.Sample7正しい

鬼滅の刃風にたとえると、Sample7 の巻物は pa の棚ではなく、pa の中の sub の棚に置かれています。
そのため、実行時にも pa.sub.Sample7 と、正しい住所で指定する必要があります。

通常のパッケージとの違い

サブパッケージを使っても、考え方そのものは通常のパッケージと大きく変わりません。

違うのは、階層が1段深くなることです。

項目通常のパッケージサブパッケージ
package 文package pa;package pa.sub;
保存先pa フォルダpa\sub フォルダ
コンパイルjavac pa\SampleX.javajavac pa\sub\Sample7.java
実行java pa.SampleXjava pa.sub.Sample7
クラスの完全名pa.SampleXpa.sub.Sample7

つまり、指定する名前やフォルダが少し長くなるだけです。

その代わり、クラスの役割を細かく整理できるようになります。

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

ここは特に大切です。

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

パッケージJavaでの扱い
papa パッケージ
pa.subpa.sub パッケージ

たとえば、次の2つのクラスがあったとします。

クラスの完全名意味
pa.DemonSlayerpa パッケージの DemonSlayer クラス
pa.sub.DemonSlayerpa.sub パッケージの DemonSlayer クラス

どちらも DemonSlayer というクラス名ですが、所属するパッケージが違うため、Javaでは別のクラスとして扱います。

鬼滅の刃風にたとえると、同じ DemonSlayer という名前の巻物でも、pa の棚にあるものと、pa.sub の棚にあるものは別の巻物です。

また、pa と pa.sub が別パッケージであることは、アクセス修飾子を学ぶときにも重要になります。

たとえば、無指定のクラスやメンバは、基本的に同じパッケージ内から使えます。
しかし、pa と pa.sub は別パッケージなので、名前が似ていても同じパッケージ扱いにはなりません。

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

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

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

鬼滅の刃風のプログラムなら、次のような分け方もできます。

パッケージ役割
pa.character鬼殺隊士や柱のクラス
pa.battle戦闘処理のクラス
pa.training修行メニューのクラス
pa.item日輪刀や道具のクラス
pa.support補助処理のクラス

このように整理すると、クラスが増えても全体の構造が見えやすくなります。

図:package pa.sub; とフォルダ構成の関係

↓クリックすると拡大表示されます。

この図が示していること

この図では、package pa.sub; とフォルダ構成の対応を表しています。

左側では、C:\Java\13 の下に pa フォルダがあり、その下に sub フォルダがあります。
Sample7.java は、その sub フォルダの中に保存されています。

中央には package pa.sub; があり、右側には pa.sub パッケージに属する DemonSlayer クラスと Sample7 クラスがあります。

要素対応
package pa.sub;pa.sub パッケージに属する
pa フォルダpa に対応する
sub フォルダsub に対応する
Sample7.javapa\sub の中に保存する

この図から分かることは、パッケージ名のピリオドが、フォルダ構成では階層として表れるということです。

図:コンパイルと実行の流れ

↓クリックすると拡大表示されます。

この図が示していること

この図では、pa.sub パッケージを使ったときのコンパイルと実行の流れを表しています。

左側には、pa\sub フォルダの中に Sample7.java が保存されています。
Sample7.java の先頭には package pa.sub; が書かれています。

中央では、次のコマンドでコンパイルしています。

javac pa\sub\Sample7.java

コンパイル後には、pa\sub フォルダの中に DemonSlayer.class と Sample7.class が作成されます。

クラスclass ファイル
DemonSlayerDemonSlayer.class
Sample7Sample7.class

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

java pa.sub.Sample7

この図から分かることは、サブパッケージを使う場合、コンパイルではフォルダ階層を指定し、実行ではパッケージ名をピリオドでつないで指定するということです。

つまずきやすいポイント

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

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

特に大切なのは、package 文、保存場所、実行時の指定を一致させることです。

項目今回の例
package 文package pa.sub;
保存場所pa\sub\Sample7.java
コンパイルjavac pa\sub\Sample7.java
実行java pa.sub.Sample7

この4つがつながると、サブパッケージの基本がかなり分かりやすくなります。

アクセス修飾子の学習にもつながる

サブパッケージを理解すると、あとで学ぶアクセス修飾子の意味も見えやすくなります。

たとえば、Javaでは、無指定のクラスやメンバは、基本的に同じパッケージ内から利用できます。

ここで、pa と pa.sub を同じパッケージだと思っていると、アクセス範囲の理解で混乱しやすくなります。

関係同じパッケージか
pa 内のクラス同士同じパッケージ
pa.sub 内のクラス同士同じパッケージ
pa と pa.sub別パッケージ

pa と pa.sub は名前がつながっています。
しかし、Javaでは別パッケージです。

鬼滅の刃風にたとえると、同じ本部内に見えても、pa の棚と pa.sub の棚は管理区画が違うということです。
そのため、アクセスできる範囲を考えるときにも、別の棚として扱う必要があります。

パッケージを階層に分けるときの大事な感覚

パッケージを階層に分けるときは、次の感覚を持つと分かりやすくなります。

考え方内容
ピリオドは階層を表すpa.sub のように書く
フォルダも階層にするpa\sub のように配置する
サブパッケージは別パッケージpa と pa.sub は同じではない
実行時は完全な名前を書くjava pa.sub.Sample7
役割ごとに整理できるクラス数が増えても見通しがよくなる

鬼滅の刃風に言えば、pa は大きな本部の棚、pa.sub はその中の補助任務専用の棚です。

Sample7.java の先頭に package pa.sub; と書くなら、実際の保存場所も pa\sub にします。
そして実行するときは、pa.sub.Sample7 と住所を最後まで指定します。

この流れが分かると、Javaのパッケージ階層はかなり自然に理解できます。