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

パッケージを階層に分ければ、Javaクラスの住所がもっと見やすくなる。大きな戦士管理本部でも、棚を分ければ迷わずクラスを探せます。

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

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

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

ドラゴンボール風にたとえると、最初はサイヤ人戦士の名簿だけを管理していればよかったものが、だんだん次のような巻物が増えていくイメージです。

巻物の種類Javaでのクラスのイメージ
サイヤ人戦士の名簿SaiyanWarrior クラス
気の技の記録KiTechnique クラス
任務の指令書BattleMission クラス
戦闘装備の台帳BattleArmor クラス
戦闘記録BattleManager クラス
補助処理の巻物SupportTool クラス

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

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

Javaでは、パッケージの中にさらに細かい分類を作れます。

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

package pa.sub;

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

ドラゴンボール風に言えば、pa という戦士管理本部の大きな棚の中に、sub という補助巻物専用の小さな棚を作るようなものです。

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

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

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

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

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

パッケージ名意味
pa大きな分類
pa.subpa の下にある sub という細かい分類
pa.battlepa の下にある battle という戦闘処理用の分類
pa.trainingpa の下にある training という修行管理用の分類

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

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

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

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

ドラゴンボール風にたとえると、pa は戦士管理本部の大きな棚です。
pa.sub は、その中にある補助任務専用の棚です。

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

サブパッケージとは何か

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

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

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

ドラゴンボール風にたとえると、戦士管理本部の棚をさらに細かく分けるようなものです。

Javaのパッケージドラゴンボール風のイメージ
pa戦士管理本部の基本棚
pa.subpa の中にある補助任務用の小棚
pa.character戦士情報用の小棚
pa.battle戦闘処理用の小棚
pa.training修行記録用の小棚
pa.item装備や道具用の小棚

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

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

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

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

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

ドラゴンボール風の学習プログラムを考えると、次のようなクラスが出てきそうです。

クラス役割
SaiyanWarriorサイヤ人戦士を表す
KiTechnique気の技を表す
BattleMission任務を表す
BattleManager戦闘の進行を管理する
TrainingMenu修行内容を管理する
SupportTool補助処理を行う

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

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

パッケージ入れるクラスの例
pa.characterサイヤ人戦士、ナメック星戦士、支援戦士など
pa.battle戦闘処理、気の技の処理など
pa.training修行メニュー、訓練記録など
pa.item戦闘装備、防具、道具など
pa.sub補助的なクラス、細かい機能のクラス

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

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

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

図:パッケージを階層に分けるイメージ

この図が示していること

この図では、すべてのクラスを pa パッケージに入れた場合と、pa の下にサブパッケージを作って整理した場合を比較しています。

左側では、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 pa.training;pa.training パッケージ

ここで大切なのは、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つのクラスを書きます。

クラス名役割
SaiyanWarriorサイヤ人戦士の名前と流派を管理する
Sample7main メソッドを持ち、処理を開始する

SaiyanWarrior は、サイヤ人戦士の情報を表すクラスです。

SaiyanWarrior の要素内容
nameサイヤ人戦士の名前
style流派
setWarrior()名前と流派を設定する
show()現在の情報を表示する

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

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

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

ファイル名:Sample7.java

package pa.sub;

// サイヤ人戦士クラス
class SaiyanWarrior
{
    private String name;
    private String style;

    public SaiyanWarrior()
    {
        name = "未登録";
        style = "流派未設定";
        System.out.println("サイヤ人戦士を準備しました。");
    }

    public void setWarrior(String n, String s)
    {
        name = n;
        style = s;
        System.out.println("戦士名を" + name + "、流派を" + style + "にしました。");
    }

    public void show()
    {
        System.out.println("戦士名は" + name + "です。");
        System.out.println("流派は" + style + "です。");
    }
}

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

実行結果

サイヤ人戦士を準備しました。
戦士名は未登録です。
流派は流派未設定です。

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

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

public SaiyanWarrior()
{
    name = "未登録";
    style = "流派未設定";
    System.out.println("サイヤ人戦士を準備しました。");
}

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

public void show()
{
    System.out.println("戦士名は" + name + "です。");
    System.out.println("流派は" + style + "です。");
}

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

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

package pa.sub;

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

クラス所属パッケージ役割
SaiyanWarriorpa.subサイヤ人戦士の情報を管理する
Sample7pa.submain メソッドで処理を開始する

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

SaiyanWarrior warrior1 = new SaiyanWarrior();

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

ドラゴンボール風にたとえると、Sample7 の任務指令書と SaiyanWarrior の戦士名簿は、どちらも pa.sub の棚にあるため、同じ棚の巻物として扱いやすいということです。

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

この図が示していること

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

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

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

要素対応
package pa.sub;pa.sub パッケージに属する
pa フォルダpa に対応する
sub フォルダsub に対応する
Sample7.javapa\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
              ├─ SaiyanWarrior.class
              └─ Sample7.class
ソース内のクラス作成される class ファイル
SaiyanWarriorSaiyanWarrior.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.SaiyanWarriorpa パッケージの SaiyanWarrior クラス
pa.sub.SaiyanWarriorpa.sub パッケージの SaiyanWarrior クラス

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

ドラゴンボール風にたとえると、同じ SaiyanWarrior という名前の巻物でも、pa の棚にあるものと、pa.sub の棚にあるものは別の巻物です。

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

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

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

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

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

ドラゴンボール風のプログラムなら、次のような分け方もできます。

パッケージ役割
pa.characterサイヤ人戦士やナメック星戦士のクラス
pa.battle戦闘処理のクラス
pa.training修行メニューのクラス
pa.item戦闘装備や道具のクラス
pa.support補助処理のクラス

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

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

この図が示していること

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

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

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

javac pa\sub\Sample7.java

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

クラスclass ファイル
SaiyanWarriorSaiyanWarrior.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のパッケージ階層はかなり自然に理解できます。