4.

クラス変数とインスタンス変数完全比較ガイド

編集
この記事の要点
  • クラス変数 (static): クラス全体で1 つだけのメモリ領域、全インスタンスで共有
  • インスタンス変数: インスタンスごとに独立したメモリ領域、N 個作れば N 個存在
  • Java: static int counter; vs int id;。アクセス: Foo.counter vs obj.id
  • Python: class_var = 0 (クラス変数) vs self.instance_var (インスタンス変数)
  • スレッドセーフ性: クラス変数は共有なので競合する。テスト困難、Singleton や Mock 化に注意

定義の違い

項目クラス変数インスタンス変数
別名静的変数 (static)メンバ変数、フィールド
メモリクラスにつき 1 つインスタンスにつき 1 つ
共有全インスタンスで共有各インスタンス独立
初期化クラスロード時に 1 回コンストラクタで毎回
アクセス (Java)ClassName.varobj.var
アクセス (Python)Class.var または self.varself.var
用途定数、カウンタ、共有設定個別の状態 (id, name)

Java での例

public class User {
    // クラス変数 (static): User クラス全体で 1 つ
    public static int totalCount = 0;
    public static final String COMPANY = "Acme Inc.";   // 定数

    // インスタンス変数: User インスタンスごとに別物
    private int id;
    private String name;

    public User(String name) {
        this.id = ++totalCount;      // クラス変数を更新 (全員で共有)
        this.name = name;            // インスタンス変数 (各自固有)
    }

    public int getId()   { return id; }
    public String getName() { return name; }
}

// 利用
User alice = new User("Alice");   // totalCount = 1, alice.id = 1
User bob   = new User("Bob");      // totalCount = 2, bob.id = 2
User carol = new User("Carol");    // totalCount = 3, carol.id = 3

System.out.println(User.totalCount);    // 3
System.out.println(alice.getId());      // 1
System.out.println(bob.getId());        // 2
System.out.println(User.COMPANY);       // "Acme Inc."

ポイント:

  • static 修飾子を付けたフィールドがクラス変数
  • 付けないフィールドがインスタンス変数
  • クラス変数は User.totalCount でクラス名経由でアクセス
  • インスタンス変数は alice.id でインスタンス経由でアクセス

Python での例

class User:
    # クラス変数: クラス本体直下に書く
    total_count = 0
    COMPANY = "Acme Inc."   # 慣習的に定数は大文字

    def __init__(self, name):
        User.total_count += 1            # クラス変数を更新
        # self.total_count = ... はNG (インスタンス変数になってしまう)

        # インスタンス変数: self.xxx で定義
        self.id = User.total_count
        self.name = name

# 利用
alice = User("Alice")    # total_count = 1, alice.id = 1
bob   = User("Bob")       # total_count = 2, bob.id = 2

print(User.total_count)   # 2
print(alice.id)           # 1
print(bob.id)             # 2
print(User.COMPANY)       # "Acme Inc."

Python の罠: self 経由代入

Python では self.x = ... で代入するとインスタンス変数が新規作成され、クラス変数を隠す挙動になります。要注意。

class Counter:
    count = 0   # クラス変数

    def increment_wrong(self):
        self.count += 1   # ❌ インスタンス変数として新規作成される
                          # self.count = self.count + 1
                          # 右辺の self.count はクラス変数を読む (0)
                          # 左辺の self.count はインスタンス変数を書く (1)

    def increment_right(self):
        Counter.count += 1   # ✅ クラス変数を更新

a = Counter()
b = Counter()
a.increment_wrong()
print(a.count, b.count, Counter.count)  # 1 0 0  ← a だけ自分の値を持つ

a.increment_right()
b.increment_right()
print(a.count, b.count, Counter.count)  # 1 1 2  ← a は自分の値(1)、b はクラス変数経由(2)

用途別の使い分け

用途選択
定数 (変更されない)クラス変数 (static final)MAX_SIZE = 100
カウンタ (生成数記録)クラス変数total_count
キャッシュ (全体で共有)クラス変数private static Map cache
Singleton インスタンスクラス変数private static Foo instance
各オブジェクトの状態インスタンス変数id / name / balance
設定値 (環境変数等)クラス変数static String dbUrl
ユーザー入力インスタンス変数String email

スレッドセーフ性の問題

クラス変数は全スレッドで共有されるため、複数スレッドから更新するとレースコンディションが発生します。インスタンス変数は通常各スレッドが別インスタンスを持つので問題は起きにくいです。

public class Counter {
    private static int count = 0;   // クラス変数

    // ❌ スレッドセーフでない
    public static void increment() {
        count++;   // 読み→更新→書きの 3 ステップで競合
    }

    // ✅ synchronized
    public static synchronized void incrementSafe() {
        count++;
    }

    // ✅ AtomicInteger (推奨)
    private static AtomicInteger atomicCount = new AtomicInteger(0);
    public static void incrementAtomic() {
        atomicCount.incrementAndGet();
    }
}

初期化タイミング

クラス変数はクラスがメモリにロードされたときに 1 回初期化されます (static initializer)。インスタンス変数は new するたびにコンストラクタで初期化されます。

public class Foo {
    // クラス変数: クラスロード時に初期化
    static int x = 10;

    // static イニシャライザ: 複雑な初期化
    static {
        System.out.println("クラスロード時に 1 回だけ実行");
        x = computeInitialValue();
    }

    // インスタンス変数: コンストラクタで初期化
    int y;
    public Foo() {
        System.out.println("new するたびに実行");
        y = 100;
    }
}

テスト困難な理由

クラス変数 (グローバル状態) はテスト間で状態が漏れるため、テストの順序依存が起きやすく、Mock 化も困難です。

public class UserCache {
    private static Map<String, User> cache = new HashMap<>();

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

// ❌ テストの順序に依存
@Test void test1() { UserCache.put("a", new User("Alice")); ... }
@Test void test2() { assert UserCache.get("a") == null; }  // test1 後だと失敗

// ✅ 各テスト前にクリア
@BeforeEach void clear() { UserCache.reset(); }

Singleton パターン

1 つしかインスタンスを作らない設計に、クラス変数を活用します:

public class DatabaseConnection {
    private static DatabaseConnection instance;   // クラス変数

    private DatabaseConnection() {}                // private コンストラクタ

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

FAQ

Q: Java の static メソッドはクラス変数のみアクセス可能?
A: はい。static メソッドからインスタンス変数を参照するとコンパイルエラー。インスタンス変数はインスタンスの存在が前提なので。

Q: Python のクラスメソッド (@classmethod) との違いは?
A: @classmethod は第一引数が cls (クラス自身) のメソッド。クラス変数操作には @classmethod が向いています。

Q: クラス変数を使うべきでないシーンは?
A: ① テスト容易性が重要、② マルチスレッド、③ DI コンテナで管理したい。これらはシングルトン Bean として依存性注入する方が現代的。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. クラスの定義
  2. initメソッド
  3. 関数の定義
  4. クラス変数とインスタンス変数

最近更新/作成されたページ