Java道|異なるパッケージのクラスを使う

別のパッケージにあるクラスは、別の隊の巻物を借りるようなものです。
public で公開し、パッケージ名.クラス名で場所を示せば、Javaのクラス連携はぐっと実践的になります。

ここまでで、クラスをファイルごとに分けること、そして同じパッケージにクラスをまとめることを学んできました。

同じパッケージにあるクラス同士は、同じ棚に置かれた巻物のように、自然に連携しやすい関係でした。

たとえば、pa パッケージの中に DemonSlayer クラスと Sample3 クラスがあれば、Sample3 から DemonSlayer をそのまま使いやすくなります。

しかし、実際のJava開発では、いつも同じパッケージのクラスだけを使うわけではありません。

大きなプログラムでは、役割ごとにパッケージを分けることがよくあります。

パッケージ役割のイメージ
pa学習用、基本クラス用
pb実行用、確認用
pc共通部品、利用されるクラス用
battle戦闘処理用
mission任務管理用

鬼滅の刃風にたとえると、鬼殺隊本部にはいろいろな部署があります。

隊士名簿を管理する棚、任務書を管理する棚、日輪刀を管理する棚、戦闘記録を管理する棚が分かれているようなものです。

このとき、pb パッケージにある実行用クラスから、pc パッケージにある鬼殺隊士クラスを使いたい場面が出てきます。

ただし、異なるパッケージのクラスは、同じパッケージのクラスのように、クラス名だけでいつでも使えるわけではありません。

Javaでは、異なるパッケージのクラスを使うときに、次の2つが大切になります。

必要なこと内容
利用されるクラスを public にする別パッケージから使えるように公開する
パッケージ名.クラス名で指定するどのパッケージのクラスかを明示する

この2つがそろうと、別のパッケージにあるクラスも正しく利用できるようになります。

異なるパッケージに分けると何が起こるのか

Javaでは、クラスを別々のパッケージに所属させることができます。

たとえば、次のように分けることができます。

クラス所属パッケージ役割
DemonSlayerpc鬼殺隊士の情報を管理する
Sample4pbDemonSlayer を使おうとする実行用クラス
Sample5pbpc.DemonSlayer を正しく使う実行用クラス

このように分けると、役割ごとにクラスを整理できます。

鬼滅の刃風にたとえると、DemonSlayer は pc の棚に置かれた隊士名簿です。
Sample4 や Sample5 は pb の棚に置かれた任務指令書です。

棚が違うため、Sample4 や Sample5 から DemonSlayer を使うには、どの棚にある DemonSlayer なのかをJavaに伝える必要があります。

同じパッケージなら、クラス名だけで見つかる場面が多いです。
しかし、別パッケージでは、クラス名だけでは場所が分かりません。

状況書き方の考え方
同じパッケージのクラスを使うクラス名だけで使いやすい
異なるパッケージのクラスを使うパッケージ名.クラス名で場所を示す

なぜそのままでは使えないのか

Javaでは、クラス名だけを書いた場合、まず同じパッケージの中にあるクラスとして探されます。

たとえば、pb パッケージの Sample4 クラスの中で、次のように書いたとします。

DemonSlayer slayer1 = new DemonSlayer();

このとき、Javaはまず pb パッケージの中に DemonSlayer クラスがあるものとして探します。

しかし、使いたい DemonSlayer クラスが pc パッケージにある場合、pb の中には DemonSlayer がありません。

そのため、Javaは DemonSlayer を見つけられず、コンパイルエラーになります。

書き方Javaが探す場所結果
DemonSlayerまず同じ pb パッケージpb にないので見つからない
pc.DemonSlayerpc パッケージ場所を明示しているので探せる

鬼滅の刃風にたとえると、pb の棚にいる隊士名簿を探しているのに、実際の隊士名簿は pc の棚にある状態です。

ただ「DemonSlayer の巻物を持ってきて」と言うだけでは、Javaは同じ棚の中を探します。
別の棚にあるなら、「pc の棚にある DemonSlayer の巻物」と住所つきで伝える必要があります。

まずは失敗する例を見てみる

まず、異なるパッケージのクラスを、そのままクラス名だけで使おうとして失敗する例を見ます。

Sample4.java は pb パッケージに属しています。
一方、使いたい DemonSlayer.java は pc パッケージに属しています。

この状態で、Sample4.java の中に DemonSlayer とだけ書くと、Javaは pb パッケージ内の DemonSlayer を探そうとします。

しかし、DemonSlayer は pc パッケージにあるため、見つけられません。

ファイル名:Sample4.java

package pb;

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

ファイル名:DemonSlayer.java

package pc;

// 鬼殺隊士クラス
public 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 + "です。");
    }
}

Sample4.java がコンパイルできない理由

作業中フォルダを C:\Java\13 として、次のようにコンパイルします。

PS C:\Java\13>javac pb\Sample4.java

すると、DemonSlayer が見つからないというエラーになります。

pb\Sample4.java:7: エラー: シンボルを見つけられません
        DemonSlayer slayer1 = new DemonSlayer();
        ^
  シンボル:   クラス DemonSlayer
  場所: クラス Sample4
pb\Sample4.java:7: エラー: シンボルを見つけられません
        DemonSlayer slayer1 = new DemonSlayer();
                                  ^
  シンボル:   クラス DemonSlayer
  場所: クラス Sample4
エラー2個

Sample4.java が pb パッケージに属しているため、DemonSlayer とだけ書くと、Javaはまず pb パッケージ内の DemonSlayer を探します。

しかし、実際の DemonSlayer クラスは pc パッケージにあります。

Sample4 の書き方Javaの判断
DemonSlayerpb パッケージ内の DemonSlayer だと考える
new DemonSlayer()pb パッケージ内の DemonSlayer を作ろうとする
結果pb に DemonSlayer がないためエラー

ここが、同じパッケージのクラスを使う場合との大きな違いです。

1つのファイルを異なるパッケージには分けられない

ここで、あわせて大切なルールがあります。

1つのソースファイルの中に、異なるパッケージに属するクラスを混在させることはできません。

理由は、1つのソースファイルには package 文を1つだけ書くからです。

たとえば、次のような書き方はできません。

// これはできない
package pb;

class Sample4
{
}

package pc;

class DemonSlayer
{
}

1つのファイルの先頭に書いた package 文は、そのファイル内のクラスの所属を決めます。

項目内容
package 文の数1つのソースファイルに1つだけ
1つのファイルに含まれるクラス基本的に同じパッケージに属する
異なるパッケージにしたい場合ファイルを分ける必要がある

鬼滅の刃風にたとえると、1本の巻物に「この巻物は pb の棚に置く」と書いたあと、途中から「ここから先は pc の棚に置く」と切り替えることはできない、ということです。

異なる棚に置きたいなら、巻物自体を分ける必要があります。

異なるパッケージのクラスを使うための条件

異なるパッケージのクラスを正しく使うには、主に2つの条件があります。

条件内容
1利用されるクラスを public にする
2利用する側で パッケージ名.クラス名 と書く

今回なら、pc パッケージの DemonSlayer クラスを、pb パッケージの Sample5 から使いたいわけです。

そのため、DemonSlayer クラスは public にします。

public class DemonSlayer

そして、Sample5 側では、DemonSlayer ではなく pc.DemonSlayer と書きます。

pc.DemonSlayer slayer1 = new pc.DemonSlayer();

このように書くことで、Javaは pc パッケージにある DemonSlayer クラスを使うのだと判断できます。

public は別パッケージから使えるようにする指定

クラスの前に public を付けると、そのクラスは別のパッケージからも利用できるクラスになります。

反対に、public を付けないクラスは、基本的に同じパッケージの中からしか使えません。

クラスの指定利用できる範囲
無指定同じパッケージ内から利用できる
public異なるパッケージからも利用できる

鬼滅の刃風にたとえると、public は「他の部署からも閲覧可能な巻物」にする指定です。

同じ棚の仲間だけで使う巻物なら、public でなくてもよい場合があります。
しかし、pb パッケージから pc パッケージの DemonSlayer を使いたいなら、DemonSlayer は外部に公開されている必要があります。

パッケージ名.クラス名で場所を明示する

異なるパッケージのクラスを使う側では、クラス名だけでなく、パッケージ名も含めて書きます。

今回なら、pc パッケージにある DemonSlayer クラスを使うので、次のように書きます。

pc.DemonSlayer

これは、pc パッケージの DemonSlayer クラスという意味です。

書き方意味
DemonSlayer同じパッケージ内の DemonSlayer として探しやすい
pc.DemonSlayerpc パッケージにある DemonSlayer を指定する

この書き方を使えば、別パッケージにあるクラスでも、場所をはっきり示して利用できます。

鬼滅の刃風にたとえると、単に DemonSlayer の巻物と言うのではなく、pc の棚にある DemonSlayer の巻物と指定するようなものです。

正しく使う例

次に、異なるパッケージのクラスを正しく使う例を見ます。

Sample5.java は pb パッケージに属しています。
DemonSlayer.java は pc パッケージに属しています。

Sample5 から pc の DemonSlayer を使うため、pc.DemonSlayer と書きます。

ファイル名:Sample5.java

package pb;

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

ファイル名:DemonSlayer.java

package pc;

// 鬼殺隊士クラス
public 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 + "です。");
    }
}

Sample5.java が正しく動く理由

Sample5.java の中心は、次の1行です。

pc.DemonSlayer slayer1 = new pc.DemonSlayer();

この1行では、型として使う DemonSlayer も、new で作成する DemonSlayer も、どちらも pc パッケージにあるものだと指定しています。

部分意味
pc.DemonSlayer slayer1slayer1 は pc パッケージの DemonSlayer 型
new pc.DemonSlayer()pc パッケージの DemonSlayer オブジェクトを作成

そのため、pb パッケージにいる Sample5 からでも、pc パッケージの DemonSlayer を使えるようになります。

さらに、DemonSlayer クラスは public です。

public class DemonSlayer

このため、別パッケージである pb からも利用できます。

条件Sample5 で満たしている内容
利用されるクラスが publicpublic class DemonSlayer
利用する側が場所を明示pc.DemonSlayer と書いている

この2つがそろっているので、Sample5 は正しくコンパイルして実行できます。

実行結果

DemonSlayer クラスのコンストラクタでは、鬼殺隊士を作成したことを表示します。

その後、show() で初期値を表示します。

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

表示内容としてはシンプルです。

しかし、大切なのは、pb パッケージの Sample5 から、pc パッケージの DemonSlayer を利用できていることです。

実行された処理内容
new pc.DemonSlayer()pc パッケージの DemonSlayer を作成
slayer1.show()作成したオブジェクトの情報を表示

異なるパッケージにあるクラスでも、正しい書き方をすれば、オブジェクトを作成し、メソッドを呼び出せます。

Sample4.java と Sample5.java の違い

Sample4.java と Sample5.java の違いを整理すると、今回の大切なポイントが見えやすくなります。

項目Sample4.javaSample5.java
所属パッケージpbpb
使いたいクラスpc の DemonSlayerpc の DemonSlayer
書き方DemonSlayerpc.DemonSlayer
利用先の明示していないしている
コンパイル失敗する成功する
理由pb の中の DemonSlayer として探してしまうpc の DemonSlayer と明示している

Sample4.java は、DemonSlayer とだけ書いています。

そのため、Javaは pb パッケージの中に DemonSlayer があるものとして探します。

一方、Sample5.java は pc.DemonSlayer と書いています。

そのため、Javaは pc パッケージの DemonSlayer を使うのだと分かります。

この違いが、異なるパッケージのクラスを使うときの中心です。

フォルダ構成

今回のファイルは、pb パッケージと pc パッケージに分かれます。

作業中フォルダが C:\Java\13 の場合、次のような構成になります。

C:\Java\13
        ├─ pb
        │  ├─ Sample4.java
        │  └─ Sample5.java
        └─ pc
           └─ DemonSlayer.java

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

フォルダパッケージ入るファイル
pbpbSample4.java、Sample5.java
pcpcDemonSlayer.java

package pb; と書いたファイルは pb フォルダへ置きます。
package pc; と書いたファイルは pc フォルダへ置きます。

鬼滅の刃風にたとえると、pb の札が付いた巻物は pb の棚へ、pc の札が付いた巻物は pc の棚へ置くということです。

コンパイルのしかた

正しく動く Sample5.java をコンパイルする場合、作業中フォルダ C:\Java\13 から次のように実行します。

PS C:\Java\13>javac pb\Sample5.java

Sample5.java の中では pc.DemonSlayer を使っています。

pc.DemonSlayer slayer1 = new pc.DemonSlayer();

そのため、pc フォルダの中に DemonSlayer.java が正しくあり、DemonSlayer クラスが public であれば、必要に応じて一緒にコンパイルされます。

コンパイル後は、次のように class ファイルができます。

C:\Java\13
        ├─ pb
        │  ├─ Sample4.java
        │  ├─ Sample5.java
        │  └─ Sample5.class
        └─ pc
           ├─ DemonSlayer.java
           └─ DemonSlayer.class

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

ソースファイルclass ファイル
pb\Sample5.javapb\Sample5.class
pc\DemonSlayer.javapc\DemonSlayer.class

実行のしかた

実行するときも、作業中フォルダ C:\Java\13 から行います。

Sample5 クラスは pb パッケージに属しています。

そのため、次のようにパッケージ名.クラス名で指定します。

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

ここで java Sample5 ではありません。

Sample5 は pb パッケージの中にあるので、pb.Sample5 と住所つきで指定します。

実行時には、pb.Sample5 が使う pc.DemonSlayer も必要です。
そのため、pc\DemonSlayer.class も正しく存在している必要があります。

public クラスのファイル名ルール

異なるパッケージから使われるクラスには public が必要です。

ただし、public クラスには大切なルールがあります。

ルール内容
public クラスは1ファイルに1つだけ1つの .java ファイルに複数の public クラスは書けない
ファイル名は public クラス名と同じpublic class DemonSlayer なら DemonSlayer.java

今回の DemonSlayer クラスは public です。

public class DemonSlayer

そのため、ファイル名は DemonSlayer.java にします。

DemonSlayer.java

ここが合っていないと、コンパイル時にエラーになります。

鬼滅の刃風にたとえると、外部にも公開する重要な巻物には、表紙の名前と中身の名前を一致させる必要がある、というイメージです。

修飾子の意味も整理しておく

異なるパッケージのクラスを使うときは、public の意味がとても大切です。

まず、クラスやインターフェイスに付ける指定を整理します。

クラス・インターフェイスの指定意味
無指定同じパッケージからのみ利用できる
public異なるパッケージからも利用できる

今回の中心は、クラスに付ける public です。

pc パッケージの DemonSlayer を pb パッケージから使うためには、DemonSlayer クラスが public である必要があります。

次に、フィールド、メソッド、コンストラクタに付ける修飾子も整理しておきます。

メンバ・コンストラクタの修飾子意味
private同じクラス内でのみアクセスできる
無指定同じパッケージからのみアクセスできる
protected同じパッケージ、または別パッケージのサブクラスからアクセスできる
publicすべてのクラスからアクセスできる

DemonSlayer クラスでは、フィールドは private にしています。

private String name;
private String rank;

これは、name や rank を外部から直接触らせないためです。

一方、コンストラクタ、setSlayer()、show() は public です。

public DemonSlayer()
public void setSlayer(String n, String r)
public void show()

これにより、別パッケージの Sample5 からでも、DemonSlayer オブジェクトを作成し、メソッドを呼び出せます。

DemonSlayer の要素修飾子役割
nameprivate外部から直接変更させない
rankprivate外部から直接変更させない
DemonSlayer()public別パッケージからオブジェクトを作れる
setSlayer()public別パッケージから情報を設定できる
show()public別パッケージから情報を表示できる

パッケージ名で同名クラスを区別できる

パッケージの大きな利点は、同じクラス名でも、パッケージが違えば別のクラスとして扱えることです。

たとえば、次のようなクラスがあるとします。

指定意味
pa.DemonSlayerpa パッケージの DemonSlayer クラス
pc.DemonSlayerpc パッケージの DemonSlayer クラス

どちらもクラス名は DemonSlayer です。

しかし、所属するパッケージが違うため、Javaでは別のクラスとして区別されます。

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

この仕組みがあるおかげで、大きな開発でも名前の衝突を避けやすくなります。

名前空間とは何か

パッケージは、名前空間としても働きます。

名前空間とは、同じ名前がぶつからないように、名前をグループごとに管理するしくみです。

DemonSlayer というクラス名だけで見ると、同名クラスが増えたときに混乱します。

しかし、パッケージ名まで含めると、区別できます。

名前区別のしかた
DemonSlayerクラス名だけなので衝突しやすい
pa.DemonSlayerpa パッケージの DemonSlayer
pc.DemonSlayerpc パッケージの DemonSlayer
mission.DemonSlayermission パッケージの DemonSlayer

つまり、パッケージは単なるフォルダ分けではありません。

クラス名を整理し、同じ名前のクラスがあっても区別できるようにする仕組みでもあります。

鬼滅の刃風に言えば、巻物の名前だけでなく、どの棚の巻物かまで含めて管理することで、取り違えを防いでいるわけです。

パッケージ名の付け方

実際の開発では、パッケージ名は自由に決められます。

ただし、他の開発者や組織と名前がぶつかりにくいように、組織のドメイン名を逆順にした形がよく使われます。

たとえば、ドメインが xxx.co.jp の組織なら、次のような形です。

jp.co.xxx

さらに、機能ごとに続けることもあります。

jp.co.xxx.battle
jp.co.xxx.character
jp.co.xxx.training
パッケージ名役割の例
jp.co.xxx.battle戦闘処理
jp.co.xxx.characterキャラクター管理
jp.co.xxx.training修行メニュー管理

鬼滅の刃風にたとえると、鬼殺隊本部の大きな住所の下に、戦闘部門、隊士管理部門、修行部門の棚を作っていくようなイメージです。

学習段階では pa、pb、pc のような短い名前でも十分です。

ただし、実務では名前の衝突を避けるために、より具体的なパッケージ名を付けることが多くなります。

図:失敗例と成功例の違い

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

この図が示していること

この図では、異なるパッケージのクラスを使うときの失敗例と成功例を比較しています。

左側の Sample4.java では、pb パッケージの中で DemonSlayer とだけ書いています。
そのため、Javaは pb パッケージ内の DemonSlayer を探します。

しかし、DemonSlayer は pc パッケージにあるため見つからず、コンパイルエラーになります。

右側の Sample5.java では、pc.DemonSlayer と書いています。
これにより、Javaは pc パッケージの DemonSlayer クラスを使うのだと分かります。

さらに、DemonSlayer クラスは public になっているため、pb パッケージから利用できます。

比較Sample4.javaSample5.java
書き方DemonSlayerpc.DemonSlayer
クラスの場所明示していないpc パッケージと明示している
結果見つからない正しく使える

この図から分かることは、異なるパッケージのクラスを使うには、住所つきで指定する必要があるということです。

図:pb パッケージから pc パッケージのクラスを使う流れ

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

この図が示していること

この図では、pb パッケージの Sample5.java から、pc パッケージの DemonSlayer.java を使う流れを表しています。

左側の pb パッケージには Sample5.java があります。
Sample5.java の中では、pc.DemonSlayer と書いています。

右側の pc パッケージには DemonSlayer.java があります。
DemonSlayer クラスは public なので、別パッケージである pb からも利用できます。

役割クラス所属
実行するクラスSample5pb
利用されるクラスDemonSlayerpc

コンパイルは作業中フォルダから次のように行います。

C:\Java\13>javac pb\Sample5.java

実行は、main メソッドを持つ Sample5 をパッケージ名付きで指定します。

C:\Java\13>java pb.Sample5

この図から分かることは、実行するクラスと利用されるクラスが別パッケージにあっても、パッケージ名を正しく示せば連携できるということです。

学習の最初につまずきやすいところ

異なるパッケージのクラスを使うときは、次のような点でつまずきやすいです。

つまずきやすい点理解のポイント
別パッケージでもクラス名だけで使えると思ってしまう異なるパッケージでは場所を明示する必要がある
public の意味があいまいpublic クラスは外部パッケージから利用できる
ファイル名のルールを忘れやすいpublic クラス名とファイル名は同じにする
同名クラスは使えないと思ってしまうパッケージ名が違えば別クラスとして共存できる
実行方法で迷うjava pb.Sample5 のようにパッケージ名付きで実行する

特に大切なのは、クラス名だけで探す場合と、パッケージ名付きで探す場合の違いです。

書き方意味
DemonSlayer同じパッケージ内の DemonSlayer として探す
pc.DemonSlayerpc パッケージの DemonSlayer を指定する

この違いが分かると、Sample4.java が失敗し、Sample5.java が成功する理由がはっきり見えてきます。

import の学習につながる大切な土台

今回の内容では、異なるパッケージのクラスを使うために、pc.DemonSlayer のようにパッケージ名.クラス名で書きました。

この書き方は、どのクラスを使っているのかがとても明確です。

ただし、何度も pc.DemonSlayer と書くと、コードが少し長くなることがあります。

そこで、次に import を学ぶと、別パッケージのクラスをより短い名前で使えるようになります。

書き方特徴
pc.DemonSlayerパッケージ名まで明示するので分かりやすい
import pc.DemonSlayer;先に宣言しておくと DemonSlayer と短く書ける

まずは、異なるパッケージのクラスを使うには、public とパッケージ名.クラス名が必要だと理解することが大切です。

この土台があると、import の意味も自然につながります。

異なるパッケージのクラスを使うときの大事な感覚

異なるパッケージのクラスを使うときは、次のように考えると分かりやすいです。

考え方内容
別パッケージは別の棚同じ棚のようにクラス名だけでは探せない
public は外部公開他のパッケージから使えるようにする
パッケージ名.クラス名は住所どの棚のどのクラスかを示す
実行時も住所が必要java pb.Sample5 のように指定する
パッケージ名は名前空間同じクラス名でも区別できる

鬼滅の刃風にたとえると、pb の任務指令書が、pc の隊士名簿を参照するようなものです。

そのためには、pc の隊士名簿が外部に公開されていて、さらに pc の棚にある DemonSlayer と住所つきで指定する必要があります。

この感覚がつかめると、Javaのパッケージ機能はかなり実践的に理解できるようになります。