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 にしない。
