Javaにおけるクラスとオブジェクトの基礎的な設計パターン

クラス設計とインスタンス生成

クラスはデータの構造と振る舞いを定義する設計図であり、実行時にメモリ上に展開される実体がオブジェクト(インスタンス)となります。

public class Product {
    String itemId;
    String category;
    
    public void showDetails() {
        System.out.println(itemId + " | カテゴリー: " + category);
    }
}

上記の設計図を元に関係性を管理するコードを実装します。

Product itemA = new Product();
Product itemB = new Product();

参照型変数とメモリアドレス

オブジェクト作成時に確保されたヒープ領域のアドレスは、参照型変数に格納されます。この変数が指す先を実際に操作することで、元のオブジェクトの状態を変更可能になります。

// 同一インスタンスへの複数参照
Product ref1 = new Product();
Product ref2 = ref1;
Product ref3 = ref1;

// 異なるインスタンスの並列生成
Product p1 = new Product();
Product p2 = new Product();

継承による機能拡張

既存のクラスを親として属性や振る舞いを引き継ぎ、新しい特性を追加する構文はextendsを使用します。重複する定義を抑え、設計の再利用性を高めます。

public class Tool {
    String name;
    int cost;
}

親クラスを引き継ぐ具体例:

public class CombatGear extends Tool {
    int attackPower;
    
    public static void main(String[] args) {
        CombatGear gear = new CombatGear();
        gear.attackPower = 45;               // 子クラス独自属性
        gear.name = "鋼鉄の剣";             // 親クラスの継承属性
        gear.cost = 2800;                    // 親クラスの継承属性
    }
}

メソッドのオーバーロード

同じメソッド名を維持しつつ、受け取る引数の型や個数を変えて複数のシグネチャを用意する手法です。直感的なAPI設計が可能になります。

public class TaxCalculator extends Calculator {
    // パラメータなし
    public void calculateRate() {
        System.out.println("基本税率を計算中...");
    }
    
    // 単一引数
    public void calculateRate(double basePrice) {
        System.out.println("対象金額: " + basePrice + " の税額算出");
    }
    
    // 複数引数
    public void calculateRate(double basePrice, String regionCode) {
        System.out.println("地域(" + regionCode + ")適用で計算完了");
    }
}

可変長引数の実装

複数のオーバーロード定義を一元化する場合は、引数の末尾に「...」を付与して配列として扱います。ループ処理などで柔軟にデータ群を制御可能です。

public class TaxCalculator extends Calculator {
    public void calculateRate() {
        System.out.println("基本税率を計算中...");
    }

    // 可変長引数(配列として処理)
    public void calculateRate(double... prices) {
        for (int i = 0; i < prices.length; i++) {
            System.out.println("[" + i + "] 金額: " + prices[i] + " 対応");
        }
    }

    public static void main(String[] args) {
        TaxCalculator calc = new TaxCalculator();
        calc.calculateRate(150.0, 275.5, 89.0);
    }
}

コンストラクタと初期化順序

インスタンス生成時自動的に呼び出され、フィールドの初期値セットやリソース確保を担当する特殊なメソッドです。戻り値の型宣言がなく、クラス名と同名であることが仕様です。

public class ServerConfig {
    String hostname;
    int port;
    
    // デフォルトコンストラクタ
    public ServerConfig() {
        System.out.println("新規サーバー設定インスタンス生成");
    }
}

複数バリアントを定義し、内部で連鎖させることも可能です。

public class ServerConfig {
    String hostname;
    int port;
    boolean secure;

    public ServerConfig(String host) {
        this.hostname = host;
    }

    public ServerConfig(String host, int localPort) {
        this(host);  // 上位コンストラクタを明示的呼出
        this.port = localPort;
        System.out.println("詳細設定が適用されました");
    }
}

thisキーワードの振る舞い

this は現在処理中のインスタンス自身を参照する予約語です。フィールド名とローカル変数名が競合する場合の区別や、他コンストラクタへの呼び出し転送に利用されます。

public class ServerConfig {
    String hostname;
    int port;

    public void printIdentity() {
        // hashCodeを表示してメモリ上の識別子が同一か検証
        System.out.println("this経由: " + this);
    }

    public static void main(String[] args) {
        ServerConfig cfg1 = new ServerConfig();
        cfg1.hostname = "db-primary.local";
        System.out.println("直接参照: " + cfg1);
        cfg1.printIdentity();
    }
}

パラメータ渡しの仕組み

Javaは原則として値渡し(Pass-by-Value)を採用しています。基本データ型は値そのものがコピーされますが、オブジェクト型の場合は「参照先アドレスのコピー」が関数内に渡ります。

public class DataStore {
    String label;
    int version;

    public DataStore(String lbl, int ver) {
        this.label = lbl;
        this.version = ver;
    }

    // 引数を別インスタンスに割り当てた場合
    public void replaceTarget(DataStore target) {
        target = new DataStore("NEW_SERVER", 2);
        System.out.println("内部更新後:" + target);
    }

    public static void main(String[] args) {
        DataStore master = new DataStore("MAIN_DB", 1);
        DataStore replica = new DataStore("COPY_DB", 1);
        replica.replaceTarget(master);
        System.out.println("外部影響なし:" + master);
    }
}

このように関数内で仮引数自体に別オブジェクトを代入しても、元の参照先の実体は変更されません。フィールドの中身を書き換える場合は、渡された参照先のメソッドを呼び出す必要があります。

static修飾子によるクラスレベルの設計

static が付与されたフィールドはクラス変数となり、全てのインスタンスでデータを共有します。各オブジェクト固有の情報は通常通りインスタンス変数として定義し、用途に応じて使い分けます。

  • インスタンス変数: オブジェクトごとに独立した値を保持(例:ユーザー名、接続ID)
  • クラス変数: システム全体で統一された値を保持(例:最大同時接続数、デフォルトタイムアウト)

アクセス権限の違いも明確です。クラス変数は ClassName.variableName で呼び出し、インスタンス生成を待たずに評価可能です。

静的メソッドの利用基準

static 修飾子を付けたメソッドはクラスメソッドと呼ばれ、特定のインスタンスに依存せずに動作します。

  • 内部的にインスタンスフィールドや非静的メソッドを一切参照しない場合、静的メソッドとして定義するのが適切です。
  • 逆に、インスタンス状態を必要とするロジックは必ず非静的メソッドとして実装してください。
  • 静的メソッドからは直接の非静的メンバにはアクセスできないため、スコープの制約を意識した設計が求められます。
public class SystemMonitor {
    private static long startTimeMillis = System.currentTimeMillis();
    private double cpuLoad;

    // 静的ユーティリティメソッド
    public static long getUptimeMs() {
        return System.currentTimeMillis() - startTimeMillis;
    }

    // インスタンスメソッド(cpuLoadを使用しているためstatic不可)
    public void refreshCpuStats() {
        this.cpuLoad = Math.random();
    }
}

タグ: Java オブジェクト指向 static修飾子 メソッドオーバーロード 可変長引数

9月8日 16:43 投稿