コンテンツにスキップ

クラスを分けて考える

〜チーム開発と拡張性を見すえたコード設計〜

■ この章のねらい

この章では、「1つの main クラスにすべて書く」やり方から一歩進んで、 複数のクラスを使った開発の基本を学びます。

  • ファイル(クラス)を分けて作る意義
  • Javaがどうやってそれらを結びつけてくれるのか
  • チームでの開発を想定した設計の考え方

を、初学者にもわかるよう、段階的に説明していきます。

1. なぜ1つのクラスだけでは足りなくなるのか?

これまで:

public class Main {
    public static void main(String[] args) {
        System.out.println(add(3, 5));
    }

    public static int add(int a, int b) {
        return a + b;
    }
}

このような1ファイル完結のプログラムでも動きますが…

  • 機能が増えていくとコードが長くなり管理しにくい
  • 複数人で分担できない(誰が何を担当するの?)
  • テストや再利用がしにくい

➡ 機能ごとにクラスを分けた方がいい

2. クラスを分けて書くとどうなる?

これまで学んだJavaのプログラムは、主に1つのクラス(たとえば Main)にすべての処理をまとめて書くスタイルでした。

ですが、実際の開発現場では、クラスを機能ごとに分割して記述するのが一般的です。たとえば、「計算をする部分」「結果を表示する部分」などを別のクラスにすることで、以下のようなメリットがあります。

■ メリット1:コードの見通しがよくなる

機能ごとにコードが整理されているので、読む人が理解しやすくなります。

■ メリット2:並行して開発しやすい

プログラムを複数のクラスに分ける=複数のファイルに分けるということです。これは、チーム開発では非常に重要です。

たとえば、

  • Aさんが Calculator.java を作る
  • Bさんが Printer.java を作る
  • Cさんがそれらを呼び出す Main.java を作る

というように、並行して開発できるようになります。これは作業の分担・効率化に直結します。

■ クラスはどうやって他のクラスを使うのか?

◎ 同じパッケージ内であれば「そのまま使える」

以下のように、クラスごとにファイルを分けた場合でも、同じフォルダ(パッケージ)内であれば、特別な設定なしで呼び出すことができます。

// Calculator.java
public class Calculator {
    public static int add(int a, int b) {
        return a + b;
    }
}
// Main.java
public class Main {
    public static void main(String[] args) {
        int result = Calculator.add(3, 5);
        System.out.println(result);
    }
}

Calculator クラスを import しなくても、そのまま Calculator.add() を使えます。

■ 異なるパッケージにある場合は?

Javaでは、関連するクラスをまとめる単位として「パッケージ(package)」という仕組みがあります。

◎ パッケージを分ける例:

// ファイル構成
src/
├── main/
│   └── Main.java
└── util/
    └── Calculator.java

Calculator.java に次のようにパッケージを宣言します:

package util;

public class Calculator {
    public static int add(int a, int b) {
        return a + b;
    }
}

Main.java 側では、import を使って読み込みます:

import util.Calculator;

public class Main {
    public static void main(String[] args) {
        int result = Calculator.add(3, 5);
        System.out.println(result);
    }
}

■ パッケージ名に「ドメインの逆」を使う理由

大規模開発では、クラスが重複しないようにパッケージ名に自分のドメイン名を逆順にして使うことが推奨されています。Javaの世界では、パッケージ名に自分の所属や団体名(Webドメインの逆順)を使うのが慣習です。

例:

package com.example.utils;

これは、世界中の誰もが自分のクラス名とパッケージ名を重複しないようにするためです。

  • com.google.search
  • org.apache.http
  • jp.co.mycompany.project.module

といったように、ユニークな名前空間を作ることで、他のプログラムやライブラリと衝突しないようにします。

■ パッケージやクラスはどうやって使われるのか?

  • Javaでは、必要になったときにクラスを読み込む仕組み(クラスローダー)があり、実行時に .class ファイルを見つけて自動的に読み込みます。
  • クラスファイルがどこにあるかを知るには、クラスパス(classpath)が使われます。

初心者はまず「同じパッケージならそのまま使える」「別パッケージなら import が必要」という2点をしっかり押さえましょう。

■ まとめ:クラスを分けることの意味

  • クラスを分けると、役割が明確になる
  • チーム開発においては、同時に作業できるようになる
  • 将来の拡張や他プロジェクトとの連携にも柔軟に対応できる

そして、これが 「オブジェクト指向」的な設計への第一歩 になります。

3. Javaはどうやってクラスを見つけてくれるのか?

クラスをファイルで分けたり、パッケージで整理したりしたとき、Javaはどのようにしてそれらのクラスを見つけて使っているのでしょうか?

実はこの裏では、JDK(Java Development Kit)に組み込まれている「クラスローダー(Class Loader)」という仕組みが働いています。

■ クラスローダーとは?

Javaプログラムは、コンパイルされると .java ファイルが .class ファイルに変換されます。プログラムを実行するとき、JVM(Java仮想マシン)は必要なクラスファイルを探し、メモリに読み込む必要があります。

このときに働くのが「クラスローダー」です。

◎ クラスローダーの種類(概念だけ紹介)

  • ブートストラップ・クラスローダー:Javaの標準ライブラリ(java.lang.* など)を読み込む
  • 拡張クラスローダー:Javaの拡張ライブラリを読み込む
  • アプリケーション・クラスローダー:自分で作ったクラスや外部ライブラリを読み込む ← 初心者がよく使うのはこれ

これらは基本的に自動で動くため、初学者がクラスローダーの詳細を意識することはあまりありませんが、裏でこのような仕組みがあることを知っておくと安心です。

■ クラスはどこにある?〜クラスパスの仕組み〜

Javaがクラスを見つけるためには、そのクラスの場所(ファイルの場所)を知っている必要があります。

このとき使われるのが「クラスパス(classpath)」です。

  • クラスパスとは:Javaが .class ファイルを探すときに参照するフォルダやJARファイルの一覧
  • クラスパスに登録されていないと、「ClassNotFoundException」などのエラーが出る

◎ クラスパスの指定方法(一例)

コマンドラインで指定する場合:

java -cp . Main

ここでは、カレントディレクトリ(.)をクラスパスとして指定しています。

■ IDE(EclipseやIntelliJ)を使っている場合

最近の開発では、ほとんどの人が Eclipse や IntelliJ IDEA といった統合開発環境(IDE)を使っています。

これらの環境では、自動的にクラスパスを管理してくれるため、初心者が意識する必要はありません。

  • クラスを新しく作ると、自動的にコンパイル&クラスパスに登録
  • 他のクラスを使おうとすると、自動で import 文を補完

という形で開発がスムーズに進められます。

■ クラス設計を意識しよう

クラスが複数になってくると、「どのクラスがどこにあるか」「どういう関係にあるか」が重要になります。

これは次の「オブジェクト指向」の考え方にも直結していて、

  • 役割ごとにクラスを分ける
  • クラス間の関係を明確にする
  • 外部のクラス(ライブラリ)も活用する

という考え方が自然と身についていきます。

■ まとめ

  • Javaは「クラスローダー」によって .class ファイルを読み込み、実行時に使っている
  • クラスパスに登録されていれば、自作クラスも外部ライブラリも利用可能
  • IDEを使えば、クラス管理やクラスパス設定を自動でやってくれるので初心者も安心
  • クラスがどう使われるかを意識することで、「設計」への感覚が育つ

4. クラスをまたいでメソッドを呼び出す:ミニプロジェクトで体験

ここでは、2つのクラスに役割を分けた小さなプログラムを実際に作りながら、クラス分割やメソッド呼び出しの流れを学んでいきます。

■ 目的:ロジックと入出力の分離

今回は、計算処理と入出力処理を別々のクラスに分けて、役割の分離を体験します。

◎ プログラムの仕様

  • ユーザーが2つの数値と演算子(+, -, *, /)を入力
  • 計算を別クラスで処理
  • 結果を画面に出力

◎ クラス構成

  • Main.java:ユーザー入力と全体の制御
  • Calculator.java:計算処理だけを担当

■ ファイル1:Main.java

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        Calculator calc = new Calculator(); // 別クラスの呼び出し

        System.out.print("1つ目の数値を入力してください: ");
        int a = scanner.nextInt();

        System.out.print("2つ目の数値を入力してください: ");
        int b = scanner.nextInt();

        System.out.print("演算子(+ - * /)を入力してください: ");
        String op = scanner.next();

        int result = 0;
        boolean valid = true;

        if (op.equals("+")) {
            result = calc.add(a, b);
        } else if (op.equals("-")) {
            result = calc.subtract(a, b);
        } else if (op.equals("*")) {
            result = calc.multiply(a, b);
        } else if (op.equals("/")) {
            result = calc.divide(a, b);
        } else {
            System.out.println("不正な演算子です。");
            valid = false;
        }

        if (valid) {
            System.out.println("結果: " + result);
        }

        scanner.close();
    }
}

■ ファイル2:Calculator.java

public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }

    public int subtract(int a, int b) {
        return a - b;
    }

    public int multiply(int a, int b) {
        return a * b;
    }

    public int divide(int a, int b) {
        if (b == 0) {
            System.out.println("0で割ることはできません。");
            return 0;
        }
        return a / b;
    }
}

■ 解説:クラスと役割の分離

  • Mainクラス:入力と出力に専念
  • Calculatorクラス:処理ロジックを担当

こうすることで、入出力部分を変えても、計算ロジックには影響が出ない構成になります。これはまさに「設計っぽい考え方」です。

■ 今後の展開に向けて

このようにクラスを分けると、次のような拡張が簡単にできます:

  • 入出力をGUI(画面)やファイルに変更しても、Calculator クラスはそのままでOK
  • Calculator に新しい計算メソッドを追加すればすぐに対応できる

この「部品を入れ替えるように開発できる」考え方が、まさにオブジェクト指向の第一歩です。

5. 練習問題

【問1】○×問題:クラスの設計に関する理解

次の文が正しければ○、間違っていれば×で答えなさい。

  1. Javaでは1つのクラスにすべての処理をまとめる方が、再利用性が高く保守しやすい。

    回答と解説

    → ×

    解説:1つのクラスに全ての処理をまとめると、再利用性が低くなり、修正や拡張もしにくくなります。機能ごとにクラスを分けることで、変更に強く保守性の高いコードになります。

  2. 別のクラスに定義されたメソッドを使いたいときは、オブジェクトを生成してから呼び出す必要がある。

    回答と解説

    → ○

    解説:通常、非staticなメソッドは、呼び出すためにそのクラスのオブジェクトを生成する必要があります。

  3. クラスを複数ファイルに分けると、複数人での並行開発が難しくなる。

    回答と解説

    → ×

    解説:逆に、クラスをファイルごとに分けることで、チーム開発において担当を分けやすくなり、並行して作業できるようになります。

  4. Javaのパッケージ名には、通常、企業や組織のドメインを逆順にした名前が使われる。

    回答と解説

    → ○

    解説:ドメインの逆順を使うことで、名前の衝突を避けることができます(例:com.example.project)。

【問2】選択問題:正しいものを選びなさい(1つ選択)

Javaにおいて、別のクラスのメソッドを使うために必要な処理として、正しいものはどれか?

A. そのクラスのメソッドをmainメソッドにコピーして貼り付ける

B. そのクラスをimportして、staticメソッドとして宣言する

C. そのクラスのインスタンスを生成し、メソッドを呼び出す

D. そのクラスの名前をコメントで書くだけで認識される

回答と解説

→ 正解:C

解説:インスタンスメソッドは、対応するクラスのオブジェクトを生成してから呼び出すのが基本です(例:Calculator calc = new Calculator();)。

【問3】穴埋め問題:次の文章の[ ]を埋めなさい

「Javaでは、1つのプロジェクトの中に複数のクラスを作成して、[ ① ]を分担させたり、[ ② ]を避けたりすることができる。」

回答と解説

→ 正解: ① 機能(役割) ② 重複(やコードの繰り返し)

解説:クラスを分けることで、それぞれのクラスが特定の役割を持つようになり、同じようなコードを何度も書かずに済みます。また、変更や拡張も柔軟になります。

【問4】選択問題:パッケージとimportに関する理解(1つ選択)

異なるパッケージにあるクラスを使用したい場合に必要な手順として、正しいものはどれか?

A. そのクラスをstaticに変更する

B. そのクラスと同じパッケージに移動する

C. import文を使ってそのクラスを明示的に読み込む

D. 何もしなくても自動で使える

回答と解説

→ 正解:C

解説:Javaでは、異なるパッケージにあるクラスを使用する際は、import パッケージ名.クラス名;と記述して明示的に読み込む必要があります。