Java入門|クラスをファイルに分ける

クラスごとにファイルを分けると、Javaのプログラムはぐっと読みやすく、育てやすくなる

ここまでの学習では、1つのファイルの中に必要なクラスを書きながら、Javaの基本的なしくみを順番に身につけてきました。小さなプログラムを作る段階では、そのやり方でも十分に学習を進められます。

ただ、プログラムが少しずつ大きくなってくると、1つのファイルの中にいろいろなクラスを書き続ける方法では、だんだん見通しが悪くなってきます。どのクラスが何の役割を持っているのかが探しにくくなり、修正したい場所を見つけるのにも時間がかかるようになります。さらに、複数人で開発するときには、同じファイルをみんなで触ることになってしまい、作業がしづらくなります。

そこで大切になるのが、クラスごとにファイルを分けるという考え方です。役割ごとに整理しておくことで、クラスの責任がはっきりし、読みやすさも保守しやすさも大きく向上します。大規模な開発では、この考え方がとても重要です。

今回は、ドラゴンボールの世界をイメージしたクラスを使いながら、クラスを別ファイルに分ける意味や、コンパイルと実行の流れをわかりやすく見ていきましょう。

クラスをファイルに分けるとは何か

Javaでは、複数のクラスをそれぞれ別のファイルに書くことができます。これは大きなプログラムを作るときの基本です。

たとえば、戦士の情報を管理するクラスと、実際にプログラムを開始するための main メソッドを持つクラスを、別々のファイルに分けておくとします。すると、それぞれの役割がはっきりします。

  • 戦士の情報を持つクラス
  • プログラムを開始するためのクラス

このように分けておくと、どのファイルを開けば何が書かれているのかがすぐわかります。開発が進んでクラスの数が増えても、整理された状態を保ちやすくなります。

なぜ1つのファイルに全部書かないほうがよいのか

小さいうちは1つのファイルでも困らないことがあります。しかし、クラスが増えると次のような困りごとが出てきます。

困りごと1つのファイルにまとめた場合ファイルを分けた場合
見つけやすさ必要なクラスを探しにくい必要なクラスのファイルをすぐ開ける
修正のしやすさ関係ない部分まで目に入る修正対象に集中しやすい
役割の整理どこまでがどのクラスの仕事か曖昧になりやすいクラスごとの責任が明確になる
複数人開発同じファイルを同時に触りやすく衝突しやすい分担しやすい
再利用一部だけ流用しにくいクラス単位で使い回しやすい

特に、チームで開発するときには、ファイル分割の大切さがよくわかります。たとえば、ある人は戦士クラスを整え、別の人は実行用クラスを作る、というように分担しやすくなるからです。

ドラゴンボール風の例で考える

今回は、戦士を表すクラスとして Fighter クラスを用います。
そして、プログラムを実行するクラスとして Sample1 クラスを用います。

Fighter クラスでは、次のような情報を管理することにします。

  • 戦士の名前
  • 戦闘力

また、情報を設定するためのメソッドとして、setFighter を用意します。
内容を表示するためのメソッドとして、show を用意します。

この構成にしておくと、Fighter クラスは「戦士の情報を表す役割」、Sample1 クラスは「処理を始める役割」と、きれいに分けられます。

ファイルを分けたときのソースコード

ここでは、2つのファイルに分けて作成します。
ファイル名とクラス名の関係にも注目してみてください。

ファイル名:Fighter.java

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

ファイル名:Sample1.java

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

このプログラムで見ておきたいポイント

この例では、Fighter クラスと Sample1 クラスを別々のファイルにしています。

それぞれの役割は次のとおりです。

ファイル名クラス名役割
Fighter.javaFighter戦士の情報を表す
Sample1.javaSample1main メソッドを持ち、処理を開始する

ここで大事なのは、Sample1.java から Fighter クラスを普通に使えていることです。
別ファイルに分けたからといって、使い方が特別に難しくなるわけではありません。

main メソッドの中では、

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

という流れで、Fighter クラスのオブジェクトを作成して、その内容を表示しています。

コンストラクタでは初期値として、

  • 名前は 未設定
  • 戦闘力は 0

が代入されています。
そのため、show を実行すると、その初期状態がそのまま表示されます。

実行結果

このプログラムを実行すると、次のような表示になります。

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

コンパイルのしかた

クラスを別ファイルに分けても、コンパイルの基本的な考え方は変わりません。
同じフォルダ内に Fighter.java と Sample1.java を保存した状態で、次のように入力します。

PS C:\Java\13>javac Sample1.java

このとき、Sample1.java の中では Fighter クラスを使っています。
そのため、同じフォルダ内に Fighter.java があれば、必要に応じて一緒にコンパイルされます。

コンパイル後には、通常は次のような class ファイルが作成されます。

  • Fighter.class
  • Sample1.class

ここでのポイントは、入口として Sample1.java をコンパイルしても、必要な関連クラスが正しくそろっていれば問題なく処理されるということです。

実行のしかた

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

PS C:\Java\13>java Sample1

実行時には、生成された class ファイルが同じフォルダ内にそろっていることが大切です。
Fighter.class が見つからないと、Sample1 の中で Fighter を使えないため、エラーになります。

つまり、ファイルを分けるときは、ソースファイルを分けるだけでなく、コンパイル後の class ファイルどうしの関係も意識することが大切です。

なぜこれまでとほぼ同じ感覚で使えるのか

ここで「ファイルを分けたのに、なぜ今までとあまり変わらないのだろう」と感じるかもしれません。

それは、Javaがクラス単位で管理する言語だからです。
私たちはソースコードをファイルに分けて整理していますが、Javaは最終的にクラスとして扱います。

そのため、同じ場所に必要なクラスがそろっていれば、

  • クラスを作る
  • メソッドを呼び出す
  • 実行する

という感覚は大きく変わりません。

つまり、ファイル分割は「動かし方を変えるため」というより、整理しやすくするための工夫だと考えると理解しやすいです。

大規模なプログラムで特に重要になる理由

大規模開発では、クラスの数が一気に増えます。
たとえば、ドラゴンボール風のアプリを作るとしても、戦士を表すクラスだけでなく、

  • 技を表すクラス
  • ステージを表すクラス
  • バトル進行を管理するクラス
  • アイテムを表すクラス
  • プレイヤー操作を受け付けるクラス

といったように、役割ごとに多くのクラスが必要になります。

これらを全部1つのファイルに書いてしまうと、読むだけでも大変です。
どこに何があるのか把握しづらくなり、修正の影響範囲も見えにくくなります。

ファイルを分けておけば、

  • 戦士に関する修正は Fighter.java
  • 技に関する修正は Skill.java
  • バトル進行に関する修正は BattleManager.java

というように、見通しを保ちながら作業できます。

ファイル分割で覚えておきたい考え方

ここでは、最初にしっかり押さえておきたい考え方を表にまとめます。

覚えておきたいこと内容
クラスは別ファイルに書ける複数のクラスをそれぞれ分けて管理できる
役割ごとに整理できるどのファイルが何を担当するか明確になる
コンパイル方法は大きく変わらない必要なクラスが同じフォルダにあれば扱いやすい
実行は main メソッドを持つクラスを指定するこの例では Sample1 を実行する
大規模開発では必須に近い考え方クラス数や担当者が増えても整理しやすい

図で流れを整理する

ファイル分割の流れは、図にするとさらにわかりやすくなります。
たとえば、次のような図を用意すると、ソースファイルと class ファイルの対応関係がひと目で理解できます。

この図では、左側にソースファイル、右側にコンパイル後の class ファイルを配置しています。
Fighter.java は Fighter クラスのもとになるファイルであり、Sample1.java は main メソッドを持つ実行開始用のクラスのファイルです。

それぞれがコンパイルされることで、

  • Fighter.java → Fighter.class
  • Sample1.java → Sample1.class

という対応になります。

そして、実行の開始地点になるのは main メソッドを持つ Sample1 クラスです。
この流れが頭に入っていると、ファイルが増えても混乱しにくくなります。

学習の段階でつまずきやすい点

初めてクラスを分割するときは、次のようなところでつまずきやすいです。

つまずきやすい点内容
ファイルの保存場所が違う同じフォルダに置かれていないと見つからないことがある
実行するクラスを間違えるmain メソッドを持たないクラスはそのまま実行できない
クラス名の意味が曖昧役割が伝わる名前にすると理解しやすい
役割が混ざるデータを持つクラスと実行開始クラスを分けると整理しやすい

特に、どのクラスに main メソッドがあるのかは、最初にしっかり確認しておくと安心です。
この例では Sample1 が入口で、Fighter は部品として使われています。

この内容が次の学習につながる理由

クラスをファイルに分けられるようになると、その先で学ぶ

  • パッケージ
  • 名前空間
  • サブパッケージ
  • インポート

といった内容も理解しやすくなります。

なぜなら、これらはすべて「増えてきたクラスをどう整理するか」という流れの上にある考え方だからです。
まずは、クラスごとにファイルを分けるという基本をしっかり身につけることが、とても大切です。

今回の内容は見た目には地味かもしれませんが、実際には大きなプログラムを作るための第一歩です。
Javaを本格的に学んでいくうえで、ここはとても大事な土台になります。