オブジェクトではなくクラスに属する
これまで書いてきたフィールドやメソッドのほとんどはオブジェクトに属しています。new を呼ぶたびに、各インスタンスはフィールドの独自のコピーを持ち、メソッドはその特定のオブジェクトの状態に対して動作します。static キーワードはこれをひっくり返します。static メンバーはクラス自体に属し、コピーは 1 つだけで全インスタンスに共有され、オブジェクトを 1 つでも作るかどうかに関係なく存在します。
このたった 1 つの違いが、static フィールド、static メソッド、定数、さらには main がなぜ常に static なのかまで説明してくれます。
共有される static フィールド
非静的フィールドは各オブジェクトに独自の場所を与えます。static フィールドはクラスに、全員で共有する 1 つの場所を与えます。典型的な例は、いくつのオブジェクトが作られたかを記録するカウンターです。
カウンターを User.count として、つまりクラス名経由で読んでいる点に注目してください。値は特定の User に属していないからです。一方で各 name は自分のオブジェクトの中に存在します。どのインスタンス経由で count を変更しても、すべてのインスタンスが新しい値を見ます。値は 1 つしかないからです。
static メソッド
static メソッドもクラスに属するので、オブジェクトなしでクラス上で呼び出します。
これはまさに標準ライブラリの背後にあるパターンです。Math.max、Integer.parseInt、Arrays.sort、List.of はすべて static であり、動作するためにオブジェクトを必要としないユーティリティ的なふるまいです。処理がオブジェクトごとの状態ではなく引数だけに依存するときは、static メソッドを選びましょう。
大きな落とし穴: static は instance を見られない
static メソッドはオブジェクトなしで実行されるため、this がありません。つまり、インスタンスフィールドに触れたりインスタンスメソッドを直接呼び出したりすることができません — それらを読み取るための特定のオブジェクトが存在しないのです。これは static で最もよくある初心者のミスです。
class Account {
int balance = 100; // インスタンスフィールド
static int show() {
return balance; // コンパイルエラー: 非静的フィールド 'balance' は
} // 静的コンテキストから参照できません
}
修正方法は、メソッドをインスタンスメソッドにする (static を外して this を持たせる) か、オブジェクトを明示的に渡すかのいずれかです。
逆方向は問題ありません。インスタンスメソッドは static フィールドを自由に読み、static メソッドを呼び出せます。クラスレベルの共有データは常に存在するからです。
static final による定数
static と final を組み合わせると定数になります。決して変わらない、共有される 1 つの値です。慣例として UPPER_SNAKE_CASE で名前を付けます。
static final は「型に属する固定値」を表す慣用的な方法です。標準ライブラリの Integer.MAX_VALUE や Math.PI のようなものです。static にすればオブジェクトごとにコピーを無駄にせず、final にすれば誰も再代入できません。
static 初期化ブロック
単純な static フィールドはインラインで初期化できます。セットアップに本格的なロジックが必要なとき — ルックアップテーブルの構築、設定の読み込みなど — には static ブロックを使います。これはクラスが最初にロードされるとき、どんなオブジェクトも存在する前に 1 回だけ実行されます。
このブロックはクラスを何回使っても 1 回だけ起動するため、一度きりのクラス全体のセットアップに最適な場所になります。
static をいつ使うか
手早い目安です。
- static フィールドは、すべてのインスタンスで本当に共有されるデータ — カウンター、キャッシュ、定数 — にだけ使いましょう。2 つのオブジェクトが妥当に異なる値を持ちうるなら、それはインスタンスフィールドにすべきです。
- static メソッドは、結果がオブジェクトの状態ではなく引数だけに依存するとき (純粋なユーティリティ関数) に使いましょう。
- 既定ではインスタンスメンバーを使いましょう。
staticを多用すると、プログラムは知らぬ間にテストしにくく理解しづらいグローバル状態の山になります。staticは例外であって、出発点ではありません。
次は: 列挙型 (Enum)
static final 定数は 1 つの固定値には適していますが、関連する値の小さく固定的な集合 — 方角、曜日、注文ステータスなど — を扱うとき、Java には散らばった定数より安全で表現力豊かな専用の型があります。それが enum であり、次のページのテーマです。
よくある質問
Java の static とはどういう意味ですか?
static は、フィールドやメソッドが個々のオブジェクトではなくクラス自体に属することを意味します。static フィールドは全インスタンスで共有されるコピーがちょうど 1 つだけ存在し、static メソッドはオブジェクトを必要とせずクラス上で呼び出します (Math.max(...))。対照的に、非静的 (インスタンス) メンバーはオブジェクトごとに新しいコピーを持ちます。
Java の static 変数とインスタンス変数の違いは何ですか?
インスタンス変数はオブジェクトごとに 1 つの値を持ちます — 2 つのオブジェクトは異なる値を保持できます。static 変数はクラスのすべてのオブジェクトで共有される単一の値を持つため、あるオブジェクト経由 (またはクラス名経由) で変更すると、他のすべてのオブジェクトから見えます。オブジェクトごとの状態にはインスタンスフィールドを、カウンターや定数のように本当にクラス全体で共通のデータには static フィールドを使いましょう。
Java の main メソッドはなぜ static なのですか?
JVM は、あなたのクラスのオブジェクトが 1 つでも存在する前に main を呼び出す必要があります。static メソッドはインスタンスではなくクラスに属するため、ランタイムは先に Main を生成せずに Main.main(args) を直接呼び出せます。シグネチャが常に public static void main(String[] args) であるのはそのためです。