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

パッケージをまたいでクラスを使えるようになると、Javaの設計はぐっと実践的になる

ここまでで、クラスをファイルごとに分けることや、同じパッケージの中にクラスをまとめることを見てきました。ここまでの段階では、関連するクラスをひとつのグループとして整理しながら使う流れが中心でしたね。

けれども、実際のJava開発では、いつも同じパッケージのクラスだけを使うとは限りません。別の機能を担当するパッケージにあるクラスを利用したり、ほかの人が作成したクラスを自分のプログラムから呼び出したりする場面がたくさんあります。大きなプログラムになるほど、このような異なるパッケージどうしの連携はとても重要になります。

ただし、異なるパッケージのクラスは、同じパッケージのときのようにそのまま気軽に使えるわけではありません。Javaでは、どのクラスを外部に公開するのか、どのパッケージのクラスを使おうとしているのかを、きちんと明示する必要があります。

今回は、ドラゴンボールの世界をイメージしたクラス名を使いながら、異なるパッケージのクラスを使うときに何が問題になり、どうすれば正しく使えるのかを、順を追ってしっかり整理していきましょう。

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

Javaでは、別々のファイルに書かれたクラスを、別々のパッケージに所属させることができます。
これは、大規模な開発ではとても自然なことです。

たとえば、戦士そのものを表すクラスを pa パッケージに入れ、実行用のクラスを pb パッケージに入れる、といった分け方ができます。こうすることで、クラスの役割ごとに所属先を整理できます。

ただし、ここで最初に押さえておきたいのは、異なるパッケージのクラスは、そのままでは利用できないという点です。
同じパッケージにいるときの感覚でクラス名だけを書いてしまうと、Javaはそのクラスを見つけられないことがあります。

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

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

ところが、使いたい Fighter クラスが pa や pc など別のパッケージにある場合、pb の中にはそのクラスがありません。
その結果、コンパイル時に「そのクラスは見つかりません」という状態になります。

ここが、同じパッケージのときとの大きな違いです。

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

最初に、異なるパッケージにあるクラスを、そのままの感覚で使おうとして失敗する例を見てみます。
ここでは、Sample4 クラスを pb パッケージに含め、別パッケージにある Fighter クラスを使おうとします。

ファイル名:Sample4.java(pbディレクトリに配置)

このコードは、一見するととてもシンプルです。
オブジェクトを作って、show メソッドを呼び出しているだけに見えます。

けれども、この書き方では正しくコンパイルできません。

package pb;

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

ファイル名:Fighter.java(pcディレクトリに配置)

package pc;

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

コンパイルのしかた

コンパイルは、作業中ディレクトリから行います。
たとえば作業中ディレクトリが C:\Java\13 なら、次のように入力します。

C:\Java\13>javac pb\Sample4.java
pb\Sample4.java:5: エラー: シンボルを見つけられません
        Fighter fighter1 = new Fighter();
        ^
  シンボル:   クラス Fighter
  場所: クラス Sample4
pb\Sample4.java:5: エラー: シンボルを見つけられません
        Fighter fighter1 = new Fighter();
                               ^
  シンボル:   クラス Fighter
  場所: クラス Sample4
エラー2個

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

Sample4.java が pb パッケージに属しているなら、この中で単に Fighter と書いたとき、Javaはまず pb パッケージの中の Fighter クラス を探します。

しかし、使いたい Fighter クラスが別のパッケージにあるなら、pb の中にはその名前のクラスは存在しません。
そのため、Javaは Fighter を解決できず、コンパイルエラーになります。

この流れを表で整理すると、こうなります。

書き方Javaが探す場所結果
Fighterまず同じパッケージ pb見つからなければエラー
pc.Fighterpc パッケージの Fighter正しく指定できる

つまり、異なるパッケージのクラスを使うときは、どのパッケージのクラスなのかを明示する必要があるのです。

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

ここであわせて覚えておきたいことがあります。
それは、1つのソースファイルの中に異なるパッケージのクラスを混在させることはできないということです。

理由はシンプルで、1つのソースファイルには package 文を1つしか書けないからです。
つまり、そのファイルに書かれたクラスは、基本的にその package 文で指定した1つのパッケージに属します。

このルールを表にすると次のようになります。

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

このため、異なるパッケージに所属させたいクラスは、必ず別々のファイルに記述します。

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

異なるパッケージのクラスを正しく使うには、次の2つが必要です。

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

この2つがそろってはじめて、別パッケージのクラスを安全に利用できます。

利用されるクラスを public にする

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

反対に、public をつけないクラスは、同じパッケージの中からしか使えません。
そのため、外部のパッケージから使ってほしいクラスには public が必要です。

パッケージ名を含めて書く

異なるパッケージのクラスを使う側では、ただ Fighter と書くのではなく、どのパッケージにある Fighter なのかを示す必要があります。

たとえば pc パッケージにある Fighter クラスなら、次のように書きます。

pc.Fighter

こうすることで、Javaは「pc パッケージの中の Fighter クラスを使うのだな」と正しく判断できます。

正しく使う例を見てみよう

では、異なるパッケージのクラスを正しく使う側の例を見てみます。
ここでは、pb パッケージに属する Sample5 クラスから、pc パッケージの Fighter クラスを使うことにします。

ファイル名:Sample5.java

package pb;

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

このコードでは、クラス名の前に pc. をつけています。
これによって、pb パッケージの中から、pc パッケージにある Fighter クラスを明示的に利用しています。

Sample5.java が正しく動く理由

Sample5.java が正しく動くのは、使いたいクラスの場所がはっきり示されているからです。

pc.Fighter fighter1 = new pc.Fighter();

この1行の中では、型としての Fighter も、new で生成する Fighter も、どちらも pc パッケージのものだと指定しています。

もし pc パッケージ側の Fighter クラスが public であれば、pb パッケージからでも使えます。
そのため、異なるパッケージにあるクラスでも、条件を満たせば問題なく利用できます。

Sample4 と Sample5 の違いを整理する

2つのコードの違いを並べると、とてもわかりやすくなります。

項目Sample4Sample5
クラスの使い方Fighterpc.Fighter
利用先の明示していないしている
コンパイル失敗する成功する
理由同じパッケージ内のクラスとして探してしまう別パッケージのクラスを明示している

この違いこそが、異なるパッケージのクラスを使う学習でいちばん大切なポイントです。

実行結果のイメージ

C:\Java\13>java pb.Sample5

pc パッケージの Fighter クラスが、たとえば次のような動きをするように作られていたとします。

  • コンストラクタで 「戦士を呼び出しました。」 と表示する
  • show メソッドで 「戦士名は未設定です。」 と表示する
  • show メソッドで「 戦闘力は0です。」 と表示する

その場合、Sample5 を実行したときの表示は、たとえば次のようになります。

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

表示内容そのものより大切なのは、異なるパッケージにあるクラスでも、正しい書き方をすればふつうにメソッドを呼び出せるという点です。

コンパイルと実行の考え方

ここでは Sample5 を実行する場面を考えてみます。
作業中ディレクトリの下に pb フォルダと pc フォルダがあり、それぞれのパッケージのファイルが置かれているとします。

イメージはこんな形です。

この状態で、作業中ディレクトリからコンパイルや実行を行います。

コンパイルの例は次のようになります。

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

実行は次のようになります。

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

ここでも大事なのは、実行時にはパッケージ名.クラス名で指定することです。
Sample5 は pb パッケージに属しているので、java Sample5 ではなく java pb.Sample5 と書きます。

public をつけたクラスのルール

異なるパッケージのクラス利用では、public の意味がとても重要です。
ここでクラスにつける public の基本を整理しておきましょう。

クラスにつける指定意味
無指定同じパッケージからのみ使える
public異なるパッケージからも使える

つまり、ほかのパッケージから利用してほしいクラスには public が必要です。

さらに、public クラスには大事なルールがあります。

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

このルールはとても大切です。
たとえば public class Fighter と書いたなら、そのソースファイル名は Fighter.java にしなければなりません。

修飾子の意味もここで整理しておこう

このあたりで、修飾子の役割も頭の中で整理しておくと理解が深まります。

クラス・インターフェイスにつける修飾子

修飾子意味
無指定同じパッケージからのみ利用できる
public異なるパッケージからも利用できる

メンバ・コンストラクタにつける修飾子

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

今回の中心はクラスに対する public ですが、今後の学習ではフィールドやメソッドの公開範囲も大切になってきます。

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

Javaのパッケージが本当に便利なのは、同じクラス名でも別のクラスとして共存できるところです。

たとえば、

  • pa.Fighter
  • pc.Fighter

という2つがあれば、どちらも Fighter という名前ですが、所属するパッケージが違うので別のクラスとして扱われます。

これは大規模開発ではとても大切です。
別々の開発者が偶然同じクラス名を使っていたとしても、パッケージ名まで含めて区別できるからです。

表で整理するとこうなります。

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

クラス名だけが同じでも、パッケージ名が違えば別物です。
このような名前の区別のしくみを、名前空間と呼びます。

名前空間とは何か

名前空間という言葉は少し難しく感じるかもしれませんが、考え方はシンプルです。
名前がぶつからないように、クラス名をグループごとに管理する仕組みだと思えば大丈夫です。

たとえば Fighter という名前だけで管理すると、同名クラスが増えたときに混乱します。
でも pa.Fighter や pc.Fighter のように、パッケージ名も含めて管理すれば、同じ Fighter でも区別できます。

つまり、パッケージは単なるフォルダ分けではなく、クラス名を整理して衝突を防ぐ仕組みでもあるのです。

パッケージ名のつけ方

実際の開発では、パッケージ名は自由に決められますが、他人とかぶりにくくするために、組織のドメイン名を逆順にした形がよく使われます。

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

jp.co.xxx

これを先頭にして、さらに機能ごとに続けていくイメージです。

jp.co.xxx.battle
jp.co.xxx.character
jp.co.xxx.training

こうすると、世界中の多くの開発者がいても、名前がぶつかりにくくなります。

図で全体の流れを整理する

この図では、上側または左側にある Fighter クラスが、別パッケージのクラスとして配置されています。
Sample4 はそのクラスを単純なクラス名だけで使おうとしているため、途中でつながらず、利用できない様子が表されています。

一方、Sample5 では、利用されるクラスが public になっていて、さらに利用する側が pc.Fighter と明示しているため、正しくつながっています。
この違いを見ることで、異なるパッケージのクラス利用に必要な条件がひと目で理解できます。

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

この内容では、次のような点でひっかかりやすいです。

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

この4つが整理できると、パッケージの理解がかなり安定します。

ここまでで押さえたいこと

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

大事なポイント内容
異なるパッケージのクラスはそのままでは使えない同じパッケージ内のクラスとしては見つからないため
利用されるクラスには public が必要外部パッケージから利用可能にするため
利用するときは パッケージ名.クラス名 と書くどのクラスを使うか明示するため
パッケージ名が違えば同名クラスも区別できる名前空間として働くため

この考え方が身につくと、Javaのパッケージ機能をかなり実践的に理解できるようになります。

次に import を学ぶと、今回の パッケージ名.クラス名 という書き方がさらに整理されて、コードをもっと読みやすく書けるようになります。ここまでの内容は、そのためのとても大切な土台です。