Java | オブジェクト指向:static とインスタンスの違い

Java Java
スポンサーリンク

全体像

static は「クラスに属するもの」、インスタンスは「オブジェクト(個体)に属するもの」です。static フィールドやメソッドは全インスタンスで共有され、クラスがロードされた時点で使えます。インスタンスフィールドやメソッドは new で作られた“その個体”の状態と振る舞いに結びつきます。設計の肝は「共有すべき事実は static、個体ごとの違いはインスタンス」にきっちり分けることです。


フィールドの違い(状態の持ち方)

インスタンスフィールド(個体ごとの状態)

各オブジェクトが独立の値を持ちます。同じクラスから作っても、値は個体ごとに別です。

public final class Counter {
    private int value;  // インスタンスフィールド

    public void inc() { value++; }
    public int value() { return value; }
}

var c1 = new Counter();
var c2 = new Counter();
c1.inc();
System.out.println(c1.value()); // 1
System.out.println(c2.value()); // 0(別個体)
Java

static フィールド(クラス全体で共有)

全インスタンスで共通の一つの値を共有します。定数、設定、作成数の記録など「全体の事実」に限定するのが安全です。

public final class Counter {
    private int value;
    private static int created = 0; // static フィールド(共有)

    public Counter() { created++; }
    public static int createdCount() { return created; }
}

var a = new Counter();
var b = new Counter();
System.out.println(Counter.createdCount()); // 2(全体共有)
Java

メソッドの違い(振る舞いの持ち方)

インスタンスメソッド(個体の振る舞い)

そのオブジェクトの状態(フィールド)を使う動作はインスタンスメソッドで表します。

public final class Rectangle {
    private final int w, h;
    public Rectangle(int w, int h) { this.w = w; this.h = h; }
    public int area() { return w * h; }  // 個体の状態を使う
}
Java

static メソッド(個体に依存しない計算)

個体の状態を使わない共通処理は static にします。引数だけで結果が決まる“純粋関数”に寄せると再利用性が高まります。

public final class MathUtil {
    private MathUtil() {}
    public static int clamp(int x, int min, int max) {
        return Math.max(min, Math.min(max, x));
    }
}
Java

static メソッドからインスタンスフィールドへは直接触れません。必要ならインスタンスを引数で受け取ります。

public final class User {
    private String name;
    public String name() { return name; }

    public static void print(User u) {         // インスタンスを受け取る
        System.out.println(u.name());
    }
}
Java

初期化・ライフサイクル・メモリの違い(重要ポイントの深掘り)

いつ初期化され、いつまで生きるか

  • static フィールド・static ブロックは「クラスロード時に一度だけ」初期化されます。アプリのライフタイムに近い長寿命です。
  • インスタンスフィールドは「new のたびに」初期化され、そのオブジェクトが到達不能(参照が切れる)になると GC により回収されます。
public final class Config {
    public static final java.util.Locale JP = java.util.Locale.JAPAN; // クラスロード時に一度
    private final java.time.Instant createdAt = java.time.Instant.now(); // new のたび
}
Java

初期化の順序と安全性

new の内部は「デフォルト値 → フィールド初期化式 → 親コンストラクタ → 子コンストラクタ」。軽い既定値はフィールド初期化式に、検証と確定はコンストラクタへ寄せると安全です。static の複雑な初期化は static ブロックへ。ただし乱用すると読みにくく、テストが難しくなります。


設計の指針と使い分け

共有すべきは static、違いはインスタンス

  • 共有の事実(定数、アプリ設定、クラス全体の統計)だけを static にする。
  • 個体の状態(属性、残高、位置、履歴)はインスタンスに閉じ込める。
  • そのオブジェクトに紐づく動作はインスタンスメソッド、共通計算は static メソッド。
public final class EmailValidator {
    private EmailValidator() {}
    private static final java.util.regex.Pattern P =
        java.util.regex.Pattern.compile("^[^\\s@]+@[^\\s@]+$"); // 共有の定数

    public static boolean isValid(String s) {                 // 共通の純粋関数
        if (s == null) return false;
        return P.matcher(s.trim()).matches();
    }
}
Java

static の乱用を避ける

static を多用して共有状態を増やすと、テストが困難になり、並行性や副作用の追跡が難しくなります。依存はコンストラクタで注入し、差し替え可能にして柔軟性を保ちましょう。


例題で身につける

例 1: 共有の既定値と個体の状態の分離

public final class Mailer {
    public static String defaultFrom = "noreply@example.com"; // 共有(必要なら final)
    private final String to;
    private final String subject;

    public Mailer(String to, String subject) {
        this.to = to;
        this.subject = subject;
    }
    public String from() { return defaultFrom; } // 共有値を参照
}
Java

defaultFrom を変えると全インスタンスに影響しますが、to/subject は各インスタンス固有です。

例 2: 集計のための static、操作のためのインスタンス

public final class BankAccount {
    private static int created = 0; // 作成数の共有集計
    private int balance;

    public BankAccount(int initial) {
        if (initial < 0) throw new IllegalArgumentException();
        this.balance = initial;
        created++;
    }
    public void deposit(int amount) { if (amount > 0) balance += amount; }
    public int balance() { return balance; }
    public static int createdCount() { return created; } // 共有情報を返す
}
Java

口座の操作はインスタンスメソッド、全体の統計は static に。

例 3: ユーティリティとしての static 純粋関数

public final class Strings {
    private Strings() {}
    public static String normalize(String s) {
        return s == null ? "" : s.trim().replaceAll("\\s+", " ");
    }
}
Java

インスタンスに依存しない共通処理は static でまとめると、依存が減り、テストが容易になります。


よくあるつまずきと回避

  • static メソッドからインスタンスフィールドへ直接触れようとして「インスタンスがない」エラーに遭遇します。インスタンスを引数で受けるか、インスタンスメソッドへ移してください。
  • なんでも public static にすると、隠蔽が壊れ、変更が難しくなります。原則として状態は private インスタンスフィールドに、操作は検証付きメソッドに。
  • 共有 mutable な static フィールドは並行性バグの温床です。可能なら final(不変)にし、必要な共有更新は同期やアトミック型を検討します。

仕上げのアドバイス(重要部分のまとめ)

static は「クラスの共有資産」、インスタンスは「個体の状態と振る舞い」。共有すべき事実だけを static にし、個体の違いはインスタンスに閉じ込める。個体の状態を使う動作はインスタンスメソッド、共通計算は static メソッドへ。初期化の順序とライフサイクルの違いを理解し、static の乱用を避ける——この使い分けが自然にできると、設計は読みやすく、変更に強くなります。

タイトルとURLをコピーしました