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

クラスごとに巻物を分ければ、Javaの戦士チームはもっと読みやすく、育てやすくなる。

Javaの学習を始めたばかりのころは、1つのファイルの中に複数のクラスを書いて、プログラムの動きを確認することがあります。

小さなプログラムであれば、この形でも学習しやすいです。
1つのファイルを開けば、クラスの定義も main メソッドも一緒に確認できるからです。

しかし、プログラムが少しずつ大きくなると、1つのファイルにすべてのクラスを書き続ける方法では、だんだん見通しが悪くなります。

ドラゴンボール風にたとえると、戦士名簿、修行メニュー、必殺技の記録、戦闘装備の台帳、出撃命令書を、すべて1本の巨大な巻物に書き込んでいるようなものです。

最初は読めても、情報が増えるにつれて、どこに何が書いてあるのか探しにくくなります。
さらに、修正したい場所を見つけるのにも時間がかかります。

そこで大切になるのが、クラスごとにファイルを分ける という考え方です。

クラスごとにファイルを分けると、次のように役割を整理できます。

ファイル役割
SaiyanWarrior.javaサイヤ人戦士の情報を表すクラスを書く
Sample1.javamain メソッドを持ち、プログラムを開始する

SaiyanWarrior.java は、サイヤ人戦士の情報を管理する巻物です。
Sample1.java は、そのサイヤ人戦士を呼び出して処理を始める出撃命令書です。

このように分けることで、どのファイルに何が書かれているのかが分かりやすくなります。

クラスをファイルに分けることは、Javaの動かし方を難しくするためではありません。
クラスの役割を整理し、読みやすく、修正しやすいプログラムにするための工夫です。

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

Javaでは、複数のクラスをそれぞれ別のファイルに書けます。

たとえば、サイヤ人戦士の情報を管理するクラスと、プログラムを開始するクラスを分けることができます。

クラスファイル名役割
SaiyanWarriorSaiyanWarrior.javaサイヤ人戦士の名前や流派を管理する
Sample1Sample1.javamain メソッドを持ち、処理を開始する

SaiyanWarrior クラスは、サイヤ人戦士の情報を表す部品です。
Sample1 クラスは、プログラムを開始する入口です。

ドラゴンボール風にたとえると、SaiyanWarrior.java は戦士の情報を記録した巻物です。
Sample1.java は、その戦士を作成して確認するための出撃指令書です。

このように分けておくと、役割がとても分かりやすくなります。

分け方イメージ
1つのファイルに全部書くすべての情報を1本の巨大な巻物に詰め込む
クラスごとにファイルを分ける戦士名簿、修行記録、出撃指令を別々の巻物にする

クラスが増えても、ファイルごとに役割が分かれていれば、整理された状態を保ちやすくなります。

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

小さいプログラムでは、1つのファイルに全部書いても、それほど困らないことがあります。

しかし、クラスが増えてくると、次のような困りごとが出てきます。

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

ドラゴンボール風にたとえると、1本の巻物にすべての情報がある状態では、戦士の流派を直したいだけなのに、修行記録や出撃命令、戦闘装備の情報まで目に入ってしまいます。

一方、戦士情報は SaiyanWarrior.java、実行開始は Sample1.java のように分けておけば、必要な巻物だけを開けます。

これは、読みやすさだけでなく、修正のしやすさにもつながります。

図:クラスごとにファイルを分けるイメージ

この図が示していること

この図では、1つの巨大なファイルに複数のクラスを書く場合と、クラスごとにファイルを分ける場合を比較しています。

左側では、すべてのクラスが1本の巻物に詰め込まれています。
この状態では、必要なクラスを探しにくく、修正したい場所も見つけにくくなります。

右側では、クラスごとにファイルが分かれています。

ファイル役割
SaiyanWarrior.javaサイヤ人戦士の情報
TrainingMenu.java修行メニュー
BattleMission.java任務情報
Sample1.java実行開始

この図から分かることは、ファイルを分けることで、クラスごとの責任がはっきりし、大きなプログラムでも見通しを保ちやすくなるということです。

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

今回は、サイヤ人戦士を表すクラスとして SaiyanWarrior クラスを使います。

SaiyanWarrior クラスでは、次の情報を管理します。

情報内容
nameサイヤ人戦士の名前
style流派

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

メソッド役割
setWarrior()戦士名と流派を設定する
show()戦士名と流派を表示する

SaiyanWarrior クラスは、サイヤ人戦士の情報を表す役割を持ちます。

一方、Sample1 クラスは main メソッドを持ち、プログラムを開始する役割を持ちます。

クラス役割
SaiyanWarriorサイヤ人戦士の情報を管理する
Sample1SaiyanWarrior オブジェクトを作り、処理を開始する

このように、クラスごとの役割を分けると、プログラムの構造が分かりやすくなります。

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

ここでは、2つのファイルに分けて作成します。

1つ目は、サイヤ人戦士の情報を表す SaiyanWarrior.java です。
2つ目は、main メソッドを持つ Sample1.java です。

ファイル名:SaiyanWarrior.java

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 + "です。");
    }
}

ファイル名:Sample1.java

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

2つのファイルの役割

このプログラムでは、SaiyanWarrior クラスと Sample1 クラスを別々のファイルにしています。

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

SaiyanWarrior.java は、サイヤ人戦士の情報を管理するクラスです。
Sample1.java は、SaiyanWarrior オブジェクトを作成して、show() を呼び出す実行用のクラスです。

ここで大切なのは、ファイルを分けても、Sample1.java から SaiyanWarrior クラスを普通に使えていることです。

main メソッドの中では、次の流れで処理しています。

SaiyanWarrior warrior1 = new SaiyanWarrior();
warrior1.show();

1行目では、SaiyanWarrior クラスから warrior1 オブジェクトを作成しています。
2行目では、warrior1 の show() を呼び出して、サイヤ人戦士の情報を表示しています。

SaiyanWarrior のコンストラクタでは、次の初期値が設定されています。

フィールド初期値
name未登録
style流派未設定

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

実行結果

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

この結果から、SaiyanWarrior オブジェクトが作成され、初期値が表示されていることが分かります。

コンパイルのしかた

クラスを別ファイルに分けても、コンパイルの基本的な考え方は大きく変わりません。

まず、同じフォルダに次の2つのファイルを保存します。

ファイル内容
SaiyanWarrior.javaSaiyanWarrior クラスを書く
Sample1.javaSample1 クラスを書く

その状態で、Sample1.java をコンパイルします。

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

Sample1.java の中では SaiyanWarrior クラスを使っています。

そのため、同じフォルダ内に SaiyanWarrior.java があれば、必要に応じて一緒にコンパイルされます。

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

ソースファイルコンパイル後のファイル
SaiyanWarrior.javaSaiyanWarrior.class
Sample1.javaSample1.class

.java ファイルは、人が読むためのソースファイルです。
.class ファイルは、Javaが実行するためにコンパイルされたファイルです。

ドラゴンボール風にたとえると、.java ファイルは人が読める戦士の巻物です。
.class ファイルは、Javaが実行できる形に変換された戦闘用の符号札のようなものです。

実行のしかた

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

この例では、main メソッドを持っているのは Sample1 クラスです。

そのため、次のように実行します。

PS C:\Java\13>java Sample1

ここで SaiyanWarrior を指定するわけではありません。

SaiyanWarrior クラスには main メソッドがないため、プログラムの開始地点にはなりません。

クラスmain メソッド実行開始クラスにできるか
SaiyanWarriorなしできない
Sample1ありできる

実行時には、Sample1.class だけでなく、Sample1 が利用する SaiyanWarrior.class も必要です。

SaiyanWarrior.class が見つからないと、Sample1 の中で SaiyanWarrior クラスを使えないため、エラーになります。

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

なぜファイルを分けても普通に使えるのか

ファイルを分けると、最初は少し不思議に感じるかもしれません。

SaiyanWarrior.java と Sample1.java に分かれているのに、なぜ Sample1 から SaiyanWarrior を普通に使えるのか、気になるところです。

理由は、Javaが最終的に クラス単位 でプログラムを扱うからです。

私たちは、読みやすくするためにソースコードをファイルに分けています。
しかし、Javaにとって大切なのは、必要なクラスが正しく見つかることです。

同じフォルダ内に必要なファイルがそろっていれば、次のような基本操作は変わりません。

処理書き方
オブジェクトを作るnew SaiyanWarrior()
メソッドを呼び出すwarrior1.show()
実行するjava Sample1

つまり、ファイル分割は、プログラムの基本的な使い方を変えるものではありません。

クラスの役割を整理し、読みやすくするための工夫と考えると分かりやすいです。

図:ソースファイルと class ファイルの関係

この図が示していること

この図では、SaiyanWarrior.java と Sample1.java を別々のソースファイルとして作り、それぞれがコンパイルされて class ファイルになる流れを表しています。

左側には .java ファイルがあります。

SaiyanWarrior.java はサイヤ人戦士情報を管理するクラスのファイルです。
Sample1.java は main メソッドを持つ実行開始用のファイルです。

中央では、javac Sample1.java によってコンパイルする流れを示しています。

右側には、コンパイル後の .class ファイルがあります。

ソースファイルclass ファイル
SaiyanWarrior.javaSaiyanWarrior.class
Sample1.javaSample1.class

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

java Sample1

この図から分かることは、ソースファイルを分けても、必要な class ファイルがそろっていれば、クラス同士は連携して動くということです。

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

大きなプログラムでは、クラスの数がどんどん増えていきます。

ドラゴンボール風の学習アプリを作るとしても、次のようなクラスが必要になるかもしれません。

クラス名役割
SaiyanWarriorサイヤ人戦士の情報を管理する
KiTechnique気の技を管理する
BattleMission任務情報を管理する
BattleManager戦闘の進行を管理する
BattleArmor戦闘装備の情報を管理する
TrainingMenu修行メニューを管理する

これらをすべて1つのファイルに書いてしまうと、読むだけでも大変です。

どこに何があるのか分かりにくくなり、修正したい場所を探すのにも時間がかかります。

ファイルを分けておけば、次のように整理できます。

修正したい内容開くファイル
サイヤ人戦士の名前や流派を修正したいSaiyanWarrior.java
気の技を修正したいKiTechnique.java
任務内容を修正したいBattleMission.java
戦闘進行を修正したいBattleManager.java
戦闘装備の情報を修正したいBattleArmor.java

ドラゴンボール風にたとえると、戦士名簿、気の技の巻物、出撃任務の指令書、戦闘装備の台帳を別々に管理するようなものです。

必要な巻物だけを開けばよいので、作業がかなり楽になります。

複数人で開発するときにも分担しやすい

ファイル分割は、複数人で開発するときにも役立ちます。

1つの巨大なファイルにすべてのクラスを書いていると、複数人が同じファイルを同時に編集することになります。

すると、作業がぶつかりやすくなります。

開発スタイル起こりやすいこと
1つのファイルに全部書く複数人が同じファイルを触り、衝突しやすい
クラスごとにファイルを分ける担当ごとに作業しやすい

たとえば、ある人は SaiyanWarrior.java を担当します。
別の人は BattleMission.java を担当します。
さらに別の人は BattleManager.java を担当します。

このように分ければ、担当範囲がはっきりします。

ドラゴンボール風にたとえると、戦士名簿係、任務管理係、装備管理係が、それぞれ別の巻物を担当するようなものです。

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

クラスをファイルに分けるときは、次の考え方を押さえておくと理解しやすくなります。

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

特に大切なのは、どのクラスに main メソッドがあるのかを確認することです。

SaiyanWarrior は部品として使われるクラスです。
Sample1 がプログラムの入口です。

この役割を分けて考えると、コンパイルと実行の流れも整理しやすくなります。

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

初めてクラスを別ファイルに分けると、いくつかつまずきやすいポイントがあります。

つまずきやすい点内容対策
ファイルの保存場所が違う同じフォルダにないとクラスが見つからないことがあるまずは同じフォルダに保存する
実行するクラスを間違えるmain メソッドを持たないクラスは実行開始できないmain を持つ Sample1 を実行する
クラス名の意味が曖昧何の役割か分かりにくいSaiyanWarrior のように役割が伝わる名前にする
役割が混ざるデータ管理と実行開始が混ざるSaiyanWarrior と Sample1 のように分ける
class ファイルが足りない実行時に関連クラスが見つからない必要な .class ファイルを同じ場所に置く

特に大切なのは、実行するクラスを間違えないことです。

SaiyanWarrior は、サイヤ人戦士の情報を表す部品です。
Sample1 は、main メソッドを持つ入口です。

そのため、実行するときは java Sample1 とします。

ファイル名とクラス名の関係

Javaでは、ファイル名とクラス名の関係も意識しておくと安心です。

今回のように、SaiyanWarrior クラスを SaiyanWarrior.java に書くと、対応関係が分かりやすくなります。

クラス名ファイル名分かりやすさ
SaiyanWarriorSaiyanWarrior.javaサイヤ人戦士クラスのファイルだと分かりやすい
Sample1Sample1.java実行用クラスのファイルだと分かりやすい

特に public class を使う場合は、public class の名前とファイル名を一致させる必要があります。

今回の例では class SaiyanWarrior のように public を付けていません。
ただし、学習を進めるうえでは、基本的に クラス名とファイル名をそろえる と考えておくと分かりやすいです。

ドラゴンボール風にたとえると、SaiyanWarrior.java という巻物には、SaiyanWarrior クラスの情報が書かれているほうが自然です。

図:main メソッドを持つクラスを実行する

この図が示していること

この図では、ファイルを分けたときに、どのクラスを実行するのかを表しています。

SaiyanWarrior は、サイヤ人戦士の情報を管理する部品クラスです。
main メソッドを持っていないため、実行開始クラスにはなりません。

Sample1 は、main メソッドを持つ実行用クラスです。
そのため、実行するときは Sample1 を指定します。

クラス役割実行開始できるか
SaiyanWarrior戦士情報を管理する部品できない
Sample1main メソッドを持つ入口できる

この図から分かることは、ファイルを分けたときも、実行時には main メソッドを持つクラスを指定する必要があるということです。

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

クラスをファイルに分けられるようになると、次の学習内容も理解しやすくなります。

次に学ぶ内容関係
パッケージクラスをさらに大きな単位で整理する
名前空間同じ名前のクラスを区別しやすくする
サブパッケージ関連するクラスを階層的に整理する
インポート別の場所にあるクラスを使えるようにする

これらは、増えてきたクラスをどう整理するかという考え方とつながっています。

まずは、クラスごとにファイルを分ける基本を押さえることが大切です。

ドラゴンボール風にたとえると、最初は戦士名簿を分けるところから始まります。
その次に、サイヤ人部隊の巻物、ナメック星支援部隊の巻物、出撃任務の巻物、装備台帳の巻物をさらに分類していくイメージです。

クラスをファイルに分けるときの大事な感覚

クラスをファイルに分けるときは、次の感覚を持つと分かりやすくなります。

考え方内容
1クラス1役割を意識するSaiyanWarrior は戦士情報、Sample1 は実行開始
ファイル名で役割が分かるようにするSaiyanWarrior.java を見れば戦士クラスだと分かる
main メソッドの場所を確認する実行するのは Sample1
関連 class ファイルをそろえるSaiyanWarrior.class と Sample1.class が必要
ファイル分割は整理のための工夫保守しやすさを高める意味が大きい

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

SaiyanWarrior はサイヤ人戦士の情報を担当する。
Sample1 はプログラムの開始を担当する。

このように役割を分けることで、1つひとつのクラスの責任がはっきりします。

そして、この考え方は、パッケージやインポートを学ぶときにもそのままつながっていきます。