Javaについて徹底解説!

Javaのstaticフィールドの使い方・活用方法! 分かりやすい解説付き!

Javaでフィールドにstaticと指定すると、クラスの持ち物であるstaticフィールド(static変数、クラス変数とも)となります。ご存じのとおり、フィールドとはローカル変数ではない変数のことです。

この記事ではstaticフィールドについて、宣言の仕方、使い方、活用例などについて初心者向けに解説します。

※この記事のサンプルは、Java 10の環境にて動作確認しています。

1.staticフィールドとはクラスが持つフィールド

インスタンスフィールドとstaticフィールドの特徴は以下のとおりです。

  • インスタンスフィールド:インスタンスを生成しないと使えない、何かのインスタンスに紐付いている、インスタンスごとに異なる変数
  • staticフィールド:インスタンスを生成しなくても使える、クラスに紐付いている、クラスで一つの変数

つまり、フィールドがstaticかどうかの違いとは、そのフィールドが紐付いている先がインスタンスとクラスのどちらであるかです。そして、Javaではクラスはメモリ上に一つしかありませんので、staticフィールドもプログラム全体で一つしかないことが保証されるのです。

1-1.良くある勘違い:staticフィールドは必ずしも定数ではない

staticは「静的な、固定の」を意味する英単語なので、「staticフィールドは一回設定した値を変えられない(=定数の役割)」と思われる方がいるかもしれません。ですが、Javaでは変数の内容を不変とする修飾子はfinalという別のものなので、間違えないようにしましょう。

staticフィールドが静的・固定であるとは、Javaのメモリ上に専用の領域を静的・固定で確保しているということです。ですから、staticフィールドは一般のプログラミング言語で言うところの広域変数であり、定数(決めた値は未来永劫変えない、変わらない)では必ずしもないのです。この理解は重要です。

staticかつfinalなフィールドであれば、プログラム全体でいわゆる定数のように用いることはできます。これは普通に使いますが、finalでないstaticなフィールドをむやみやたらに使うと、過去のプログラミングで大問題となった広域変数の問題がJavaでも再発するので、大変危険です(詳細は後述します)

2.staticフィールドの宣言の仕方

フィールドをstaticフィールドとするには、変数宣言において型の「前」に修飾子 static を付けるだけです。

変数名はインスタンスフィールド・staticフィールド間でも重複してはいけませんので、以下はコンパイルエラーとなります。

また、ローカル変数をstaticにはできませんので、以下もコンパイルエラーとなります。

3.staticフィールドの使い方

インスタンスフィールドとstaticフィールドは何の持ち物かが違うので、使い方も以下のように違います。なお、同じクラスの中あるいはstatic importを行えば、staticフィールドを使う際のクラス名.”は記述を省略できます。

  • インスタンスフィールド:インスタンスを指す変数名.インスタンスフィールド名
  • staticフィールド:クラス名.staticフィールド名

フィールドが属する先が違うだけなので、使い方そのものはインスタンスフィールドと同じです。

staticフィールドはクラスで一つだけ、というのは以下でも確認できます。どこかでstaticフィールドの値を変更すると、プログラム上でそのフィールドを参照している箇所全てで変更結果が反映されるのです。

なお、staticフィールドもインスタンス経由で参照できます。しかし、staticフィールドだとプログラム上で分かりづらくなりますので、通常はクラス名から参照します。開発環境によっては警告が出ることすらあります。

4.staticフィールドの初期化の仕方

staticフィールドの初期化の仕方は以下の2つがあります。

  • 宣言時に値を設定する
  • staticイニシャライザで値を設定する

なお、未初期化とした場合の初期値のルールはインスタンスフィールドと同じで、数値型プリミティブの場合は0booleanの場合はfalse、参照型の場合はnullです。

4-1.宣言時に値を設定する

インスタンスフィールドやローカル変数と同様に、宣言時に値を設定できます。staticフィールドはクラスに一つですから、初期値が設定されるのはクラスの初期化と同タイミング、かつプログラム実行中に1回だけです。

4-2.staticイニシャライザで値を設定する

インスタンスはコンストラクタ内でフィールドの初期化などを行えますが、クラスそのものにはコンストラクタがありません。

コンストラクタの代わりに、staticイニシャライザというクラスの初期化に使える構文がありますので、そこで値を設定できます。staticイニシャライザはブロックなので、長い処理も書けます。一方、宣言と同時の初期化では、一つの文で書ける内容でしか初期化できないのです。

staticイニシャライザとは、クラスの中に以下の書式で作成したブロックのことです。“static”の後ろにブロックを置き、ブロック中に必要な処理を書きます。finalstaticフィールドで、宣言時に値を設定していない場合は、staticイニシャライザ内で初期化する必要があります。

staticイニシャライザではチェック例外をthrowできません。しかも、staticイニシャライザ内で発生した例外がcatchせずにthrowされるなら、そのクラスは永久に初期化が完了できず、プログラム中から使えません。適切にcatchするなどしなければなりません。

ちなみに、活用できる局面は少ないのですが、staticイニシャライザは一つのクラス中に複数記述できます。複数記述した場合は、記述した順番で(上から)実行されます。

5.staticフィールドの活用例

5-1.定数として使う

最も一般的なstaticフィールドの使い方です。Javaでは定数はfinal修飾子で実現しますが、単にfinalなだけだと特定のインスタンスでしか使えない定数になり、インスタンスを生成しないと使えないなど使い勝手が悪いので、一般的にはstaticと組み合わせます。

staticかつfinalなフィールドを定数として使うことは実務でも一般的に行います。ですが、お互いに関係のない定数がずらりと並んだ、いわゆる定数クラスや定数インターフェイスは「Javaらしい」ものではありません。クラスの存在意義である分割統治や適切な役割分担に真っ向から反する使い方だからです。あくまで必要悪です。それに、定数を使うのであれば、今ならenumも積極的に活用しましょう。

また、以下のとおり参照型変数をstatic finalとしても、変数が指している先のインスタンスが保持している値が固定されることとはイコールではありませんので、こちらも意識する必要があります。

5-2.ロック(排他制御)用の変数として使う

クラスから生成された全インスタンスでの排他制御を行う際のロック用オブジェクトとして、staticフィールドを使うことがあります。

staticと指定しなかった場合は同一インスタンス内での同期実行が保証されますが、別のインスタンスも含めた同期は行えません。そのためには、インスタンスを超えたところにロック用のオブジェクトが必要で、そのためにstaticフィールドを使います。

今では排他制御にはjava.util.cuncurrent.locksのクラスを使うことが普通ですが、その場合でもクラス単位での排他制御が必要なら、staticフィールドを用います。

5-3.1つだけのインスタンスを保証する(Singletonパターン)

あるクラスのインスタンスが1つだけしかないことを保証したいケースがあります。その場合に良く使うのはデザインパターンのSingletonですが、これはstaticフィールドの活用事例としても有名なものです。

staticフィールドに自分のクラスのインスタンスを持ち、さらにコンストラクタはprivateとして外部からのインスタンス生成を不可能とします。インスタンスを取得するためのstaticメソッドを作り、その中だけでインスタンスを生成すれば、Singletonパターンの出来上がりです。

Singletonに限らず、フィールドに自分自身のクラスを持つ、というのが初心者には分かりづらいかもしれませんが、こういう書き方もOKなのです。

5-4.固定的なインスタンスのキャッシュに使う

Javaは必要に応じていくらでもインスタンスをnewできるのがいいところですが、それでもインスタンスの生成を制限したり、インスタンスを使いまわしたいケースがあります。その場合にもstaticフィールドが活用できます。

例えば、Integerはインスタンスを使いまわします。Integerintのラッパークラスで、何かの整数を表現するクラスです。Integer自体は不変(Immutable)なので、同じ数字(例えば1)なのに違うインスタンスを都度生成してはメモリの無駄です。ですので、staticフィールドでインスタンスを使いまわしています。

以下はJavaのInteger.valueOf(int)のソースコードです。IntegerCacheというクラスのstaticフィールドはIntegerの配列で、valueOfの引数で指定された整数が配列の範囲内であれば、そこのインデックスにあるIntegerのインスタンスを戻しています。newするのは配列の範囲外の整数の場合だけです。

実際にvalueOfで戻るIntegerが同じインスタンスか確認してみましょう。小さな整数(01とか)はプログラムでは良く使われるので、キャッシュを利用してインスタンス生成を効率化しているのですね。この考えは、デザインパターンのFlyweightパターンにも通じるものがあります。

6.staticフィールドの悪用例

staticフィールドはいわゆるグローバル変数的には使うべきではありません。例えば以下のような感じです。

なぜstaticフィールドをこのように使ってはならないのかというと、これは昔からあるグローバル変数(広域変数)の使い方そのものであるからです。グローバル変数は手軽に使えて便利な反面、以下のデメリットがあり、数多くのバグを生み出してきました。

  • どこでそのグローバル変数が使われているか把握するのが難しい
  • 誰がどこでグローバル変数の値を変更したかが簡単には追いかけられない
  • グローバル変数の設定値が正しいと保証するのが難しい(マルチスレッド環境だと顕著)

そもそもJavaなどのオブジェクト指向プログラミング言語は、このような課題を改善するために生まれてきました。値をグローバル変数ではなくクラスに持たせて変数を管理する主体を明確にし、かつ変数のカプセル化によりそれを徹底するのが、オブジェクト指向プログラミング言語での考え方です。

staticフィールドをグローバル変数的に使えるからと言って、過去のプログラミング言語のような使い方をしていては、なくせるバグもなくせません。せっかくJavaを使うのですから、バグが少なくなるプログラムの作り方をしたいものですね。

7.まとめ

staticフィールドとは、クラスの持ち物であるフィールドです。使い方そのものはインスタンスフィールドと似ていますが、クラス唯一の変数であることは常に念頭に置いておきましょう。

staticフィールドの使い方・活用方法は様々で、便利に使えます。ですが、むやみやたらにグローバル変数的に用いることはプログラム言語の進化の意味に反することでもあるので、どのように使うかを良く考えましょう。