15.

Java static キーワード完全ガイド

編集
この記事の要点
  • static はインスタンスに属さず、クラス自体に属するメンバ(変数 / メソッド / 内部クラス)を作るキーワード
  • static 変数 (クラス変数): 全インスタンスで 1 つの値を共有。Counter.count のようにクラス名でアクセス
  • static メソッド: new せず呼べる。Math.sqrt() / Integer.parseInt() が典型
  • static 初期化ブロック: クラスロード時に 1 回だけ実行。設定読み込みに使う
  • 欠点: グローバル状態 / テストが難しい / オーバーライドできない(隠蔽されるだけ)。シングルトンや純粋関数的なユーティリティに限定

static とは何か

static は Java(C# / C++ も同様)のキーワードで、クラス全体で共有されるメンバを宣言します。インスタンス(new したオブジェクト)ごとに別の値を持つ通常メンバとは対照的です。

種類所属アクセス方法
インスタンス変数各オブジェクトobj.field
static 変数クラス全体ClassName.field
インスタンスメソッド各オブジェクトobj.method()
static メソッドクラス全体ClassName.method()

static 変数 (クラス変数)

public class Counter {
    public static int total = 0;    // ★ static 変数(全インスタンス共有)
    private int id;                  // インスタンス変数(個別)

    public Counter() {
        total++;                     // 共有値をインクリメント
        this.id = total;
    }
}

Counter c1 = new Counter();    // total = 1, c1.id = 1
Counter c2 = new Counter();    // total = 2, c2.id = 2
Counter c3 = new Counter();    // total = 3, c3.id = 3

System.out.println(Counter.total);    // ★ クラス名でアクセス: 3
System.out.println(c1.id);            // 1

static メソッド

インスタンス化せずに呼べるメソッド。状態を持たない純粋関数に向きます。

public class MathUtils {
    public static int square(int n) {
        return n * n;
    }

    public static double average(int[] nums) {
        int sum = 0;
        for (int n : nums) sum += n;
        return (double) sum / nums.length;
    }
}

// new せずに呼べる
int sq = MathUtils.square(5);                      // 25
double avg = MathUtils.average(new int[]{1,2,3});  // 2.0

// 標準ライブラリの例
double pi = Math.PI;                  // static 定数
double s = Math.sqrt(16.0);           // static メソッド
int n = Integer.parseInt("42");       // static メソッド
String s2 = String.valueOf(123);      // static メソッド

static の制約

制約説明
this 不可static メソッドからは this を使えない(インスタンスが無い)
インスタンスメンバアクセス不可static から非 static フィールドを直接参照できない
オーバーライド不可static は隠蔽 (hiding) されるだけ。多態性なし
abstract と併用不可abstract static は無意味(abstract は実体オーバーライド前提)

static 初期化ブロック

クラスがロードされる時に1 度だけ実行されるブロック。複雑な初期化に。

public class Config {
    public static final Map<String, String> SETTINGS;

    static {
        // クラスロード時に 1 回だけ実行される
        SETTINGS = new HashMap<>();
        SETTINGS.put("env",   System.getenv("APP_ENV"));
        SETTINGS.put("debug", System.getenv("APP_DEBUG"));
        // ファイルから設定読込なども可能
    }

    // インスタンス初期化ブロック(static 無し)は new ごとに実行
    {
        System.out.println("instance init");
    }
}

static インナークラス (Nested Class)

外部クラスのインスタンスに依存しない内部クラス。普通の内部クラス (inner class) と違い、外部の this を持たないのでメモリリークを防げます。

public class Outer {
    private int x;

    // ❌ 普通の内部クラス: Outer の this を暗黙保持 → リーク要注意
    class Inner {
        void show() { System.out.println(x); }  // 外部 x にアクセス可
    }

    // ✅ static インナークラス: Outer のインスタンス無しで作れる
    static class StaticInner {
        void show() { System.out.println("hi"); }
        // System.out.println(x); ← ★ コンパイルエラー: static から非 static 不可
    }
}

// 普通の内部: 外部インスタンスが必要
Outer.Inner inner = new Outer().new Inner();

// static: 外部不要
Outer.StaticInner si = new Outer.StaticInner();

static import

毎回 Math. と書きたくない場合に使う構文(Java 5+)。

// 通常
import java.lang.Math;
double a = Math.PI * Math.sqrt(16);

// static import
import static java.lang.Math.PI;
import static java.lang.Math.sqrt;
double b = PI * sqrt(16);    // クラス名省略可

// ワイルドカード
import static java.lang.Math.*;
double c = PI * sqrt(16) + abs(-3);

// 注意: 乱用するとどのクラスのメソッドか分からなくなる
// JUnit5 の assertEquals / assertTrue 等で常用される
import static org.junit.jupiter.api.Assertions.*;

@Test
void testAdd() {
    assertEquals(4, 2 + 2);    // クラス名不要で読みやすい
}

シングルトンパターンと static

public class Logger {
    private static Logger instance;    // ★ static フィールドで唯一性を保証

    private Logger() { }    // 外部から new 禁止

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

    public void log(String msg) {
        System.out.println("[LOG] " + msg);
    }
}

Logger.getInstance().log("hello");

ユーティリティクラス

状態を持たない static メソッドの集まり。private コンストラクタでインスタンス化を防ぐのが定石。

public final class StringUtils {
    private StringUtils() {    // ★ インスタンス化禁止
        throw new AssertionError("no instances");
    }

    public static boolean isBlank(String s) {
        return s == null || s.trim().isEmpty();
    }

    public static String reverse(String s) {
        return new StringBuilder(s).reverse().toString();
    }
}

StringUtils.isBlank("  ");    // true
// StringUtils s = new StringUtils();  ← コンパイル可だが実行時 AssertionError

static のメモリ管理

領域JVM バージョン説明
PermGenJava 7 以前クラス定義 / static フィールドが入る固定領域。サイズ不足で PermGen space エラー
MetaspaceJava 8+ネイティブメモリに移動。デフォルト無制限(最大値設定可: -XX:MaxMetaspaceSize

static フィールドはクラスがアンロードされるまで解放されません。巨大なオブジェクトを static に保持するとメモリリークの原因に。

static のアンチパターン

  • グローバル状態の濫用: テストごとに状態がリセットされず、テスト独立性が失われる
  • static でないとできない処理: ほとんどないので、可能なら DI で渡す
  • static 変数で動作変更: ある画面でセットした値が別画面に影響
  • Singleton の濫用: テストでモック注入が困難。代わりに DI コンテナを推奨

FAQ

Q: static メソッドはなぜテストしにくい?
A: モック化が困難(Mockito 標準ではモック不可、PowerMock や mockito-inline で対応)。可能ならインスタンスメソッドにして DI で差し替えられるように。

Q: static finalfinal static の違いは?
A: 順序は任意で同じ意味。慣習的に static final の順。public static final で「クラス定数」。

Q: PHP の static:: は?
A: 遅延静的束縛 (Late Static Binding)。self:: は宣言クラス、static:: は呼び出し元クラスを指します。継承時の挙動が変わります。

編集
Post Share
子ページ
  1. staticと非staticの違い
同階層のページ
  1. 基本的なルール
  2. データ型
  3. 変数
  4. 定数
  5. 配列
  6. コレクション(List,Set,Queue)
  7. Map(連想配列)
  8. 演算子
  9. 条件分岐
  10. 繰り返し制御文
  11. クラス
  12. メソッド
  13. インスタンス化
  14. コンストラクタ
  15. staticキーワード
  16. オーバーロード
  17. 継承
  18. オーバーライド
  19. this
  20. super
  21. パッケージ
  22. アクセス修飾子
  23. 抽象クラス・メソッド
  24. インターフェース
  25. カプセル化
  26. データベース接続
  27. セッション
  28. ファイル入出力
  29. ラムダ式

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