こんにちは、NRIネットコムの小畑です。
4月から業務の傍ら、週1でJava(Spring Boot)の社内研修を受けています。
その研修の中で学んだことをこのブログにてアウトプットしていこうと思います。
今回は、研修の中で少し理解に苦しんだJavaのstatic修飾子についてまとめます。
staticとは
「static」とは「静的な」という意味で、メソッドとフィールドに付けることができます。ここでいう「静的な」とは、変わることのないもの、クラス間で共有されるものであるものを指します。
staticの有無でメソッドとフィールドは下記のように呼び方が変わります。
- クラスメンバ:
- staticメソッド(クラスメソッド):static修飾子が付いているメソッド
- static変数(クラス変数):static修飾子が付いているフィールド
- インスタンスメンバ:
- インスタンスメソッド:static修飾子が付いていないメソッド
- インスタンス変数:static修飾子が付いていないフィールド
クラスメンバとインスタンスメンバの違いとして、「クラスメンバはインスタンスを作成しなくとも利用できる」のに対し、「インスタンスメンバはインスタンスを作成しないと利用できない」という違いがあります。
また、staticメソッドとインスタンスメソッドそれぞれのクラスとの関連性は、下記のような違いがあります。
- staticメソッド: クラス自体に関連付けられます。インスタンス化することなく、直接クラス名を使って呼び出すことができます。
- インスタンスメソッド: インスタンス化されたオブジェクトに関連付けられます。オブジェクトを生成してから使用する必要があります。
staticメソッドの活用方法
ここまで文章で「static」について説明しましたが、いまいち掴みづらいと思うので、具体例を用いて説明します。
public class Counter { // static変数 private static int count = 0; // インスタンス変数 private int instanceCount = 0; // staticメソッド public static int staticIncrement() { return count++; } // インスタンスメソッド public int instanceIncrement() { return instanceCount++; } }
上記の例では、Counterクラスにはcountというstatic変数とinstanceCountというインスタンス変数があります。staticIncrementメソッドはcount変数の値をインクリメントし、instanceIncrementメソッドはinstanceCount変数の値をインクリメントします。
public class Controller1 { public static void main(String[] args) { Counter counter1 = new Counter(); // counter1というCounterオブジェクトを生成 Counter counter2 = new Counter(); // counter2というCounterオブジェクトを生成 System.out.println(Counter.staticIncrement()); // 結果: 1 System.out.println(counter1.instanceIncrement()); // 結果:1 System.out.println(counter2.instanceIncrement()); // 結果:1 System.out.println(Counter.staticIncrement()); // 結果:2 System.out.println(counter1.instanceIncrement()); // 結果:2 System.out.println(counter2.instanceIncrement()); // 結果:2 } }
Controller1では、始めに3,4行目でインスタンスメソッドを利用するためにcounter1, counter2というCounterオブジェクトを生成し、それぞれインスタンス化しています。
その後に6行目でstaticメソッドのstaticIncrementメソッドを使ってcount変数の値をインクリメントしています。次に、7,8行目でcounter1とcounter2それぞれのinstanceIncrementメソッドを呼び出してinstanceCount変数の値をインクリメントしています。これらの全てのインクリメント出力結果は1となります。
続いて、同様に11~13行目でそれぞれのインクリメントを実行すると、先ほどの出力結果の1に+1するのでこちらも全ての結果が2と出力されます。
public class Controller2 { public static void main(String[] args) { Counter counter1 = new Counter(); // counter1というCounterオブジェクトを生成 Counter counter2 = new Counter(); // counter2というCounterオブジェクトを生成 System.out.println(Counter.staticIncrement()); // 結果: 3 System.out.println(counter1.instanceIncrement()); // 結果:1 System.out.println(counter2.instanceIncrement()); // 結果:1 System.out.println(Counter.staticIncrement()); // 結果:4 System.out.println(counter1.instanceIncrement()); // 結果:2 System.out.println(counter2.instanceIncrement()); // 結果:2 } }
次に、Controller2でController1と同一のインクリメントの処理を行います。Controller2では、6,11行目のstaticIncrementメソッドを使ってcount変数の値をインクリメントした場合のみ、Controller1と結果が異なっています。これは変数countがstaticであるため、クラス全体で共有されており、Controller1, Controller2それぞれで同一のcountをインクリメントしているためです。counter1, counter2は、それぞれController1, Controller2内でインスタンス化し、インスタンス毎の変数を所持しているためコントローラー毎にインクリメントされています。
Singletonパターンでの利用
staticメソッドを活用したデザインパターンにSingletonと呼ばれるデザインパターンがあります。設定ファイルのリソース管理をする場合などの、あるクラスのインスタンスが1つしかないことを保証する場合にSingletonを利用することができます。
public class Singleton { // 唯一のインスタンスを持つstatic変数 private static Singleton instance = new Singleton(); // privateのコンストラクタ private Singleton() { } // static変数の値をreturn public static Singleton getInstance() { return instance; } }
static変数に自分のクラスのインスタンスを持ち、さらにコンストラクタはprivateとすることで外部からのインスタンス生成を不可能としています。インスタンスを取得するためのstaticメソッドを作り、生成したインスタンスを返却させたものをSingletonパターンと呼んでいます。
まとめ
メソッドやフィールドを定義する際に使用する「static修飾子」についてまとめました。文字列の操作や日付フォーマットへの適用変換する際などの他からの影響を受けることなく処理をさせる場合にstaticメソッドを利用することが望ましいと考えています。また、ドメイン情報などの範囲がグローバルな設定情報などのプログラムが終了するまで残しておきたい情報がある場合にはstaticメソッドを使用することが望ましいと考えています。
正直、これまで「static」は静的なものを扱う時に使うものと認識はしていたものの、Javaでメソッドを記述する際には公式のように「public static~」とあまり深く考えずに書いていました。しかし、研修で改めて学習したことで「static修飾子」の意味について理解したうえで記述しなければ、使いづらく、読みづらいソースコードとなると気づきました。メモリについても考えながら、パフォーマンスを高められるコーディングができるように今後も研修に励みたいと思います。