この内容は古いバージョンです。最新バージョンを表示するには、戻るボタンを押してください。
バージョン:5
ページ更新者:atom
更新日時:2026-06-11 07:07:02

タイトル: staticクラスとシングルトンの違い
SEOタイトル: static クラス vs Singleton 完全比較 (Java / C# / Python の実装視点)

この記事の要点
  • static クラス: インスタンスを持たず、クラスメソッド・クラスフィールドのみで動作。Math.PI / Collections.sort()
  • Singleton: インスタンスを 1 つだけ生成し、グローバルにアクセスさせる
  • 違い: 状態の持ち方 / インターフェース実装 / 継承可否 / Mock しやすさ / Lazy 初期化
  • 純粋関数の集約は static / 状態を持つ・差替え可能性ありは Singleton が定石
  • 現代の正解: DI コンテナ + Singleton スコープ でテスト容易性と疎結合を両立

結論を先に

観点static クラスSingleton
インスタンス無し (クラス自体)1 つ (this あり)
状態クラスフィールド (グローバル)インスタンスフィールド
呼び出しClassName.method()ClassName.getInstance().method()
インターフェース実装不可
継承不可 (Java の static、C# の static class)
多態性不可
Mock困難 (PowerMock 等)容易 (DI で差替)
Lazy 初期化クラスロード時に確定初回 getInstance() 時に生成可
用途ユーティリティ / 純粋関数状態 / リソース管理

static クラス (Java)

// Java の static utility クラス例
public final class MathUtils {
    public static final double PI = 3.14159265;

    private MathUtils() { }   // インスタンス化禁止

    public static double square(double x) {
        return x * x;
    }

    public static double sum(double[] arr) {
        double s = 0;
        for (double v : arr) s += v;
        return s;
    }
}

// 使用 (インスタンス化せずクラス名から呼ぶ)
double s = MathUtils.square(5);
double pi = MathUtils.PI;

Java の標準 API では java.lang.Math, java.util.Collections, java.util.Arrays がこのパターンです。

Singleton (Java)

public class Logger {
    private static volatile Logger instance;
    private final List buffer = new ArrayList<>();  // 状態を持つ

    private Logger() { }

    public static Logger getInstance() {
        if (instance == null) {
            synchronized (Logger.class) {
                if (instance == null) instance = new Logger();
            }
        }
        return instance;
    }

    public void log(String msg) {
        buffer.add(LocalDateTime.now() + " " + msg);
    }

    public List getBuffer() {
        return Collections.unmodifiableList(buffer);
    }
}

// 使用
Logger logger = Logger.getInstance();
logger.log("started");

違い 1: インスタンス・状態

  • static はクラス全体で 1 つの「クラスレベルのデータ」。this が存在しない
  • Singleton は 1 つの「インスタンスレベルのデータ」。this が使える

状態を持たず純粋計算するだけなら static、状態 (リスト・接続・設定) を保持して操作するなら Singleton が向きます。

違い 2: インターフェース実装

interface Notifier {
    void send(String msg);
}

// ❌ static クラスは interface を実装できない
public final class StaticMailNotifier {
    public static void send(String msg) { ... }
}
// → 「StaticMailNotifier implements Notifier」ができない

// ✅ Singleton なら interface 実装可能
public class MailNotifier implements Notifier {
    private static final MailNotifier INSTANCE = new MailNotifier();
    public static MailNotifier getInstance() { return INSTANCE; }
    @Override public void send(String msg) { ... }
}

// 使用側はインターフェース型で受けられる → 差替え可能
Notifier notifier = MailNotifier.getInstance();
notifier.send("hi");

違い 3: 継承

Java の static フィールド・メソッドは継承できない (オーバーライドできない)。C# の static class も継承不可。Singleton はインスタンスメソッドのため通常のクラスと同じく継承・オーバーライド可能です。

// Singleton は継承で振る舞いを切り替えられる
public class BaseLogger {
    public void log(String msg) { System.out.println(msg); }
}

public class SlackLogger extends BaseLogger {
    @Override public void log(String msg) {
        super.log(msg);
        sendToSlack(msg);
    }
}

// テスト環境 / 本番環境で getInstance() の中身を切り替え可能

違い 4: Mock しやすさ (テスト)

// ❌ static は Mock しにくい
public class OrderService {
    public void notify(String msg) {
        MailUtils.sendMail(msg);   // ← static 呼び出し、テストで差替できない
    }
}

// ✅ Singleton + interface で差替容易
public class OrderService {
    private final Notifier notifier;
    public OrderService(Notifier notifier) {  // コンストラクタ注入
        this.notifier = notifier;
    }
    public void notify(String msg) {
        notifier.send(msg);
    }
}

// テストでは Mock 注入
Notifier mock = mock(Notifier.class);
OrderService svc = new OrderService(mock);
svc.notify("test");
verify(mock).send("test");

違い 5: Lazy 初期化

static フィールドは クラスロード時に初期化されるため、使わなくても初期化コストがかかります。Singleton は getInstance() 初回呼び出しまで遅延 (Lazy) できます:

// 初期化に 5 秒かかる重い処理
public class HeavyConfig {
    private static final HeavyConfig INSTANCE = new HeavyConfig();
    private HeavyConfig() {
        // 5 秒かかる初期化
    }
    public static HeavyConfig getInstance() { return INSTANCE; }
}
// → クラス参照時に 5 秒待たされる

// Lazy 版
public class HeavyConfig {
    private static HeavyConfig instance;
    private HeavyConfig() { /* 5秒 */ }
    public static synchronized HeavyConfig getInstance() {
        if (instance == null) instance = new HeavyConfig();
        return instance;
    }
}
// → 初回 getInstance() でのみ 5 秒

C# での比較

// static クラス
public static class MathUtil {
    public const double PI = 3.14159;
    public static double Square(double x) => x * x;
}

// Singleton (Lazy 推奨)
public sealed class Logger {
    private static readonly Lazy lazy = new(() => new Logger());
    public static Logger Instance => lazy.Value;
    private List buffer = new();
    private Logger() {}
    public void Log(string m) => buffer.Add(m);
}

Python での比較

Python は module 自体がシングルトンなので、static と singleton の区別は実装上ほぼ無く、書き方の問題:

# static 相当 (module-level 関数)
# math_utils.py
PI = 3.14159
def square(x): return x * x

# Singleton 相当 (クラス + __new__)
class Logger:
    _inst = None
    def __new__(cls):
        if cls._inst is None:
            cls._inst = super().__new__(cls)
            cls._inst.buf = []
        return cls._inst
    def log(self, m): self.buf.append(m)

# 実用上、状態を持たないなら module-level 関数 (= static 相当)
# 状態を持つなら singleton クラスか、module-level の変数

使い分けの指針

シーン推奨
純粋関数の集約 (数学関数、文字列操作)static
状態 (キャッシュ・接続プール・ロガー)Singleton
差替・テスト容易性が必要Singleton + interface
定数の集約static final / const
DI フレームワーク利用環境DI コンテナの Singleton スコープ
Lazy 初期化が必要Singleton
サブクラスで振る舞い変更Singleton

アンチパターン: 状態を持つ static クラス

// ❌ 状態を持つ static クラス (グローバル変数と同じ問題)
public final class SessionStore {
    private static Map sessions = new HashMap<>();

    public static void put(String id, User u) { sessions.put(id, u); }
    public static User get(String id) { return sessions.get(id); }
}

// 問題:
// - テストごとに sessions がリークする
// - スレッドセーフでない (ConcurrentHashMap でも問題は残る)
// - Mock しにくい
// - 依存先がコードに埋め込まれる

// ✅ Singleton か DI コンテナで管理
public class SessionStore {
    private final Map sessions = new ConcurrentHashMap<>();
    // ... DI コンテナで Singleton として登録
}

現代的な妥協点: DI コンテナ + Singleton スコープ

Spring、Laravel、NestJS 等のフレームワークは、コンテナでオブジェクトのライフサイクルを管理します。Singleton パターンを自前で書くのではなく、コンテナの Singleton スコープに登録するのが現代の正解です。

  • テストで容易に差替 (Mock 注入)
  • スコープ切替 (Singleton / Request / Prototype) が柔軟
  • 循環依存の早期検知
  • getInstance() をコードから消せる (コンストラクタ注入)

FAQ

Q: ユーティリティクラスは static で書くべきか?
A: 状態を持たない純粋関数群なら static で OK (Math / Collections)。テストもしやすい。状態が入る瞬間に Singleton か DI コンテナへ移行を。

Q: static メソッドはテストできない?
A: テスト対象が static を呼んでいると Mock 困難 (PowerMockito / Mockito 5+ の static モックで可能だが推奨されない)。Singleton + DI で差替が王道。

Q: Singleton と static、メモリ消費に差は?
A: ほぼ同じ。どちらもプロセス内に 1 つ。Lazy 初期化なら Singleton の方が起動直後のメモリ少なめ。

Q: 言語によってどちらが好まれる?
A: Java は両方使われる (Math は static、ロガーは Singleton)。C# は static class が言語サポートで Java より使いやすい。Python は module-level が一般的。PHP は近年 DI コンテナ (Laravel / Symfony) 経由が主流。