Java 逆引き集 | static とインスタンスの違い — 状態管理

Java Java
スポンサーリンク

Static とインスタンスの違い — 状態管理

「static はクラスに属する」「インスタンスはオブジェクトに属する」。この一言が本質です。どの状態を“全体共有”にするか、“個別”にするかで、設計の見通しが大きく変わります。初心者向けに、使い分けと落とし穴をコード例でかみ砕いて説明します。


基本概念(クラスに属するか、オブジェクトに属するか)

  • static(クラスメンバ):
    • 所属: クラスそのもの。インスタンスを作らずに使える。
    • 用途: 共有カウンタ、定数、ユーティリティ関数、キャッシュ。
    • 寿命: クラス読み込みからアンロードまで。プロセス全体で共有。
  • インスタンス(フィールド/メソッド):
    • 所属: new で作る各オブジェクト。個別の状態を持つ。
    • 用途: ユーザー、注文、設定など“個別データ”。
    • 寿命: 参照がある間だけ。ガーベジコレクションで解放。

まずはコード例(見て直感で掴む)

class Counter {
    // 全インスタンス共有の合計
    static int total = 0;

    // インスタンスごとの個別カウント
    int count = 0;

    void inc() {
        count++;
        total++;
    }
}

Counter a = new Counter();
Counter b = new Counter();
a.inc(); b.inc(); b.inc();

System.out.println(a.count);   // 1(aだけの状態)
System.out.println(b.count);   // 2(bだけの状態)
System.out.println(Counter.total); // 3(全体共有)
Java
  • ポイント:
    • static はクラス名からアクセス(Counter.total)。
    • インスタンスの状態はインスタンスからアクセス(a.count)。

static メソッドとインスタンスメソッド

class MathUtil {
    static int add(int x, int y) { return x + y; }   // 状態を持たない純粋関数
}

class Cart {
    int items;

    void addItem() { items++; }                      // 個別状態を更新
}
Java
  • 使い分け:
    • 状態に依存しない処理: static メソッド(ユーティリティ)。
    • 個別の状態を扱う処理: インスタンスメソッド。

よくある現場パターン

  • 定数の定義(final + static):
    • 使い方: Const.TAX_RATE のように参照。定数は大文字+アンダースコアが慣習。
class Const {
    static final double TAX_RATE = 0.10;
    static final String APP_NAME = "Shop";
}
Java
  • ユーティリティクラス(インスタンス化禁止):
final class Strings {
    private Strings() {}                     // 生成禁止
    static boolean isBlank(String s) {
        return s == null || s.trim().isEmpty();
    }
}
Java
  • インスタンスで状態保持(ドメインモデル):
class Order {
    private int qty;
    void add(int n) { qty += n; }
    int getQty() { return qty; }
}
Java

ありがちな落とし穴と回避策

  • 落とし穴: なんでも static にしてしまい、テスト困難・設計硬直。
    • 回避: 共有すべき根拠があるものだけ static に。ロジックは注入可能(DI)に。
  • 落とし穴: static フィールドを複数スレッドが更新して競合。
    • 回避: 不変(final)にする、または 同期/アトミック型を使用。共有ミューテーションは最小化。
class Global {
    static final AtomicInteger total = new AtomicInteger(0);
    static void inc() { total.incrementAndGet(); }
}
Java
  • 落とし穴: ライフサイクル管理不能(キャッシュのメモリリーク)。
    • 回避: 明確なクリア手順を用意、スコープを狭める、必要なら弱参照やスコープ付きコンテキストへ。
  • 落とし穴: static メソッドからインスタンスフィールドへ直接触ろうとして失敗。
    • 回避: インスタンスが必要なら引数で受け取る、またはメソッドをインスタンス化側に置く。

設計の指針(状態管理の観点)

  • 状態が“共有”か“個別”かを先に決める: 共有なら static、個別ならインスタンス。
  • 不変を好む: 共有値は final で不変に。変更が必要なら責務を限定(カウンタなど)。
  • 依存の向き: static に依存すると差し替えが難しい。テスト容易性を重視し、インターフェース+インスタンス注入を優先。
  • main が static な理由: プログラム開始時点にインスタンスがないため、クラスから直接呼び出す必要がある。

例題で身につける(解説つき)

  • 例題1: 税率は共有、数量は個別
    • ポイント: 共有定数は static、個別状態はインスタンス。
class Pricing {
    static final double TAX = 0.10; // 共有
    static int withTax(int price) { return (int) Math.round(price * (1 + TAX)); }
}
class Item {
    int qty;                          // 個別
    void add(int n) { qty += n; }
}
Java
  • 例題2: ユーティリティ vs ドメインの責務分離
    • ポイント: 整形はユーティリティ(static)、レコードはインスタンスが持つ。
final class Dates {
    private Dates() {}
    static String ymd(int y, int m, int d) { return String.format("%04d-%02d-%02d", y, m, d); }
}
class Invoice {
    String id;
    int amount;
    String issuedAt;                   // インスタンスが保持
    Invoice(String id, int amount, String issuedAt) {
        this.id = id; this.amount = amount; this.issuedAt = issuedAt;
    }
}
Java
  • 例題3: グローバルカウンタの安全化
    • ポイント: 共有可変状態は競合対策を入れる。
class Stats {
    private static final AtomicInteger totalOrders = new AtomicInteger(0);
    static void inc() { totalOrders.incrementAndGet(); }
    static int get() { return totalOrders.get(); }
}
Java

テンプレート集(すぐ使える雛形)

  • 定数定義(共有・不変)
public final class Consts {
    private Consts() {}
    public static final int MAX_RETRY = 3;
    public static final String DATE_FMT = "yyyy-MM-dd";
}
Java
  • ユーティリティ(副作用なし)
public final class Numbers {
    private Numbers() {}
    public static int clamp(int v, int min, int max) {
        return Math.max(min, Math.min(max, v));
    }
}
Java
  • インスタンス状態(ドメインモデル)
public class Account {
    private int balance;
    public void deposit(int yen) { balance += yen; }
    public int getBalance() { return balance; }
}
Java

まとめ

  • static は“クラスに属する共有”、インスタンスは“オブジェクトに属する個別”。
  • 共有は不変を基本に、変更が必要なら競合対策を。
  • ユーティリティは static、個別データはインスタンスに。
  • テストと拡張性を意識して、なんでも static にしない。

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