Javaについて徹底解説!

Javaのstaticメソッドを丁寧に解説! 活用例や考え方も一緒に学ぼう!

Javaのクラスが持てるメソッドは、メソッドの所有者の違いの観点からすると二種類あります。static修飾子を適用したstaticメソッドと、staticではないメソッド、すなわちインスタンスメソッドです。

staticメソッドはクラスに直接属し、クラスが実行するメソッドです。インスタンスに属し、インスタンスが実行するインスタンスメソッドとは異なるものです。使いどころももちろん異なります。

このstaticメソッドは必要性があり存在するのですが、初級者にとっては理解が難しいものです。なぜわざわざ二種類のメソッドがあるのか分かりづらいですよね? この記事ではそんな人のために、staticメソッドの基礎からお伝えします。

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

1.staticメソッドはクラスが実行するメソッド

Javaのメソッドは必ず何かのクラスに属します。その上で、メソッドの実行者の違いの観点からは、以下の二つに分けられます。

インスタンスメソッド:インスタンスが持つメソッド、インスタンスが実行する

staticメソッド(クラスメソッド):クラスが持つメソッド、クラスが実行する

  ※クラスメソッドとも呼ばれますが、この記事中ではstaticメソッドで統一します

これ以外のメソッドの分類には抽象メソッドかどうかなどがありますが、staticメソッドの理解は、まずは誰が実行するメソッドなのかに注目するところからスタートします。

1-1.staticメソッドはインスタンスに依存しない処理

皆さんは、インスタンスとはクラスをひな型として生成(new)された具体的なモノという知識はお持ちでしょう。ですから、インスタンスへのメソッド呼び出しはイメージがしやすいかと思います。インスタンスへ何かの処理を実行するよう指示するイメージです。

一方、ひな型でしかないクラスが持つメソッドを呼び出すとはどういうことなのか、なかなかイメージはしづらいです。ただ、これは現実世界への比喩をプログラミング言語の世界に無理に持ち込もうとしているからイメージしづらいのであって、「インスタンスに依存しない処理」のことなのだと割り切っても良いでしょう。

前述のとおり、Javaのメソッドは何かのクラスに属する必要があります。インスタンスの持ち物でないならば、クラスの持ち物としようというだけなのです。プログラムを作っているとインスタンスが存在しない状態でもメソッド(=処理)を呼び出したいことが多々あります。その時に使うのがstaticメソッドなのです。

2.staticメソッドの宣言の仕方

staticメソッドの宣言の仕方は、メソッド宣言での戻り値の前に “static” を付けるだけです。これだけでこのメソッドはクラスの持ち物になります。

最も大事なのは、staticメソッドの中ではインスタンスに属するモノは一切参照できませんし、実行もできないことです。具体的には、インスタンスフィールド・インスタンスメソッド・staticではない内部クラス・this/superは使えません。使うとコンパイルエラーになります。

staticメソッドを実行するクラス自身はインスタンスではないので、インスタンスフィールド・メソッドを持っていないからです。ですから、staticメソッドではstaticフィールドや他のstaticメソッド、引数を使ってプログラムを行います。

なお、インスタンスの持ち物を直接参照・実行できないというだけであって、間接的になら大丈夫です。つまり、staticメソッドの中で自クラスのインスタンスを生成したり、引数として受け取るのなら普通に使えます。

その場合でも、あくまで個別のインスタンスが持つフィールド・メソッドにアクセスしているだけということは忘れないようにしましょう。

3.staticメソッドの使い方

宣言したstaticメソッドを呼び出すには、原則としてクラス名.staticメソッド名と指定します。前述したとおり、staticメソッドはクラスの持ち物ですから、クラス名を経由してメソッドを呼び出すのです。

ちなみに、staticメソッドを宣言したクラス内やサブクラス内で使うなら、クラス名部分の指定は不要です。インスタンスのコンテキストからも同じように呼び出せます。

なお、インスタンス(を指す変数)経由でもstaticメソッドは呼び出せます。しかし、クラスの持ち物を呼び出すのですからクラスを経由するのが自然ですし、普通です。

しかも、開発環境によっては、インスタンス経由のstaticメソッド呼び出しは警告扱いになることがあります。例えば、Eclipseでは以下のような警告が出ることがあります。コンパイルエラーではないので動かす分には問題ありませんが、あまり良い書き方ではないということですね。

4.staticメソッドの注意事項

4-1.static・インスタンスメソッドのシグネチャは同じにできない

staticメソッドとインスタンスメソッドのシグネチャ(メソッド名、引数の型・並び順)は同じにはできません。すなわち、以下のメソッド宣言は行えず、コンパイルエラーとなります。

4-2.staticメソッドのアクセス修飾子はインスタンスメソッドと同じ

private/protected/publicのアクセス修飾子の働きはインスタンスメソッドと同じです。以下のいずれも正しいものです。

4-3.staticメソッドはabstractにはできない

staticメソッドは抽象メソッド(abstract)とはできません。後述しますが、staticメソッドはクラスの継承の対象外ですので、abstractとすることに意味がないからです。

4-4.【中級者向け】staticメソッドは継承されない

何回もお伝えしたとおり、staticメソッドはクラス固有の持ち物です。これは、staticメソッドはサブクラスへ継承されないということを意味します。

以下の例では、スーパークラス・サブクラスで同じシグネチャのstaticメソッドが存在します。インスタンスメソッドならサブクラスでオーバーライドしたものが使われますが、staticメソッドではどのクラスのものか明示しないなら、スーパークラスのstaticメソッドが呼ばれます。

このような動きになるのは、Javaの継承ではインスタンスメソッドはサブクラスでオーバーライドされますが、staticメソッドはされないためです。この動きからも、staticメソッドはクラスのものだということが分かるかと思います。

5.staticメソッドの活用方法

5-1.ユーティリティメソッドとしての活用

staticメソッドの最も普通の使い方は、インスタンスの状態に依存しない処理をさせることです。これはユーティリティメソッドとも呼ばれ、決まりきった処理などに用いられます。標準APIではStringIntegerなどにも多くのstaticメソッドがありますし、CollectionsArraysなどにあるものもよく使いますよね。

StringやIntegerなどでは、インスタンスメソッドとstaticメソッドが上手く使い分けされています。StringIntegerはそれぞれのインスタンスが自分自身の文字列や数値を持つ一方で、StringIntegerに関する共通的な処理もプログラムでは必要になります。

5-1-1.例:String.substring()String.valueOf()

例えば、Stringのインスタンスメソッドとstaticメソッドには以下のものがあります。

substringはString自らが持つ文字列から切り出しますので、インスタンスメソッドがふさわしいです。SQLなどではsubstring(文字列, 開始位置, 切り出し文字数)などとしますが、JavaではString自身に「あなたが持っている文字列から指定位置を切り出したものをください」と頼むのですね。

一方、valueOfintに対応するStringを得るメソッドです。この処理にはStringのインスタンスが持つ文字列は関係ありません。つまり、何かのStringのインスタンスに「このintに対するStringを生成してください」と頼んだとして、インスタンス自身が持つ文字列は結果に何も影響しないのです。

ですから、String.valueOf(int)はインスタンスではなくクラスの持ち物すなわちstaticメソッドであるのがふさわしいのです。intに対するStringを生成するのは、インスタンスに関係のない決まりきった内容の処理だからです。

5-1-2.ユーティリティクラスだとprivateコンストラクタで明言する

なお、staticメソッドだけ持つクラスは、インスタンスとして生成されなくても機能を提供できます。それを徹底するため、コンストラクタをprivateとして外部からインスタンスを生成できないようにして、ユーティリティクラスであることを明確に示すことも普通です。

この例として、先述したCollectionsArraysは、コンストラクタがprivateになっています。もしstaticメソッドばかりで、しかもprivateなコンストラクタのクラスを見かけたら、それはユーティリティクラスなのだと考えてください。

5-2.【中級者向け】staticファクトリーメソッドとしての活用

staticファクトリーメソッドとは、インスタンスを組み立てる処理を代行し、結果として組み立て済みのインスタンスを返すメソッドのことです。デザインパターンでのFactory Methodとは内容が違いますのでご注意ください。

newするだけのインスタンス生成をわざわざstaticメソッドで?…と思われるかもしれません。ですが、実は様々なメリットがあります。この節でもいくつか紹介しますが、詳細を知りたい方は、例えば書籍「Effective Java」などに詳細が記載されていますので、ぜひ読んでみましょう!!

なお、Integer.valueOf(int)staticファクトリーメソッドの側面も持っています。内部の実装ではIntegerがキャッシュされていて、特定の範囲の数値に対しては、常にキャッシュされた同じIntegerのインスタンスが戻るのです。このように、Javaの内部でもstaticファクトリーメソッドは広く使われています。

5-2-1.インスタンス生成後の初期化処理をまとめて実行する

例として、普通はインスタンスを使う側がnewして必要な処理を呼び出しますが、その工程が複雑だとミスが起きますので、そこまでワンセットにして提供するパターンがあります。

5-2-2.インスタンス生成処理に「意味」を付与する

応用として、意味や文脈をもって用途に応じたインスタンスを取得させるのにも使えます。例えば以下のようにすると、メソッド名で何を目的にしたインスタンスを戻すのかが明確になります。使う側がインスタンスをnewして必要な設定をするよりもずっと分かりやすいですよね。

それに、クラスにコンストラクタがたくさんある場合、それぞれのJavadocを読み込んでコンストラクタの意味を調べるよりも、メソッド名でどんな状態を持つインスタンスが戻ってくるか直感的にわかる方が、使う側にとってはより使いやすいと思いませんか?

5-2-3.サブクラスを意識させないインスタンス生成

さらに、上手にクラス設計ができていれば、固有の実装はサブクラスで行った上で、staticファクトリーメソッドを用いてインスタンスを生成するクラスを意識させないで済ませられます。

これだと、使う側はどのサブクラスを使うべきかを知らなくても、どのstaticメソッドを呼び出せばいいのか知っておくだけでいいのですね。

5-3.【中級者向け】Singletonパターンでの利用

staticファクトリーメソッドの応用の一つとして、あるクラスのインスタンスが1つだけしかないことを保証できます。その場合に良く使うのはデザインパターンのSingletonですが、これはstaticメソッドの活用事例としても有名なものです。

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

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

6.インターフェイスのstaticメソッド

Java 8から、インターフェイスにstaticメソッドを宣言できます。さらに、Java 9からはそれをprivateにもできます。ですから、そのインターフェイスに関連する処理をインターフェイス自身に宣言できるようになりました。defaultメソッドと併せて、使いようによってはとても便利ですね。

ですから、以下のように外部からも直接呼び出せます。インターフェイスを実装したクラスからも呼び出せますが、実装クラスからは直接呼び出せず、インターフェイス名を経由する必要があるのが玉に瑕です。

これができるようになったのは、Java 8で導入された関数型インターフェイスやラムダ式にも関係があります。関数型インターフェイスは何かの「機能」を表現したものですが、それを活用・応用した共通的・汎用的なメソッドが宣言されるべき場所は、やはりそのインターフェイス自身がふさわしいからです。

Java 7まではインターフェイスには実装を持てなかったので、インターフェイスを活用するには別のユーティリティ的なクラスが必要でした。例えば、Collectionに対するCollectionsですね。でも、このように分かれていると、CollectionとCollectionsの両方を覚えないといけませんし、Collections自体がどんどん肥大化していきます。それだと作る方も使う方も大変ですよね。

Java 8以降での標準APIでは、このようなインターフェイス自身が持つstaticメソッドが大量に追加されました。便利なものも多いので、どんどん使っていきましょう。

7.【中級者向け】staticメソッドとインスタンスメソッドの使い分けの考え方例

staticメソッドとインスタンスメソッドの使い分けの考え方は良く議論となります。個々のプログラマーのプログラミング経験や、プログラミングへの姿勢の違いで意見が分かれますし、それぞれの理由には納得感があるので、中々合意はされません。これからも全員が合意できるものは出て来ないでしょう。

ですので、ここではあくまで私見を述べます。皆さんも自分の考えを持ってみて、周囲のプログラマーと意見を交わしてみてください。

publicなメソッドはインスタンスメソッドを原則とするのが、Javaでは自然だと感じます。Javaではプログラムの状態はインスタンスで表現するのが自然で、プログラム全体が数多くのインスタンスの相互作用で表現されるからです。それに、staticメソッドは処理の本筋から少し離れたものとの感覚があります。

staticメソッドに「してもよい」と感じるのは、privateなスコープ内での共通的な処理で、インスタンスの状態に依存しないものです。privateなのでstaticメソッドでもインスタンスメソッドでもクラス外部への影響はなく、staticなのでインスタンスに依存しない処理だと明示できるからです。

staticメソッドに「すべき」と感じるのは、引数とstaticフィールドだけで完結する処理で、かつクラス内外で頻繁に使う決まりきった内容の処理です。いわゆる「関数」や「プロシージャ」的なものです。何かの決まりきった処理のために何の状態も持たないクラスのインスタンスを生成して都度使い捨てるのは、メモリやCPU時間の無駄だと感じます。

8.まとめ

以上、staticメソッドの説明をしてきました。メソッドをstaticとすることでクラスの持ち物になり、インスタンスとは関係のない文脈でいつでもそのメソッドを使えるようになります。

staticメソッドはユーティリティ的にも使えますし、様々なデザインパターンでも重要な役割を持つものです。でも、むやみやたらと使うと読みづらく、使いづらいプログラムになりますので、インスタンスメソッドとの使い分けはよくよく考える必要があるのです。

メソッドをクラスの持ち物にするというだけではピンとこなかったかもしれませんが、この記事での活用例を通じてstaticメソッドはこんな風に使えるんだ、と感じていただけたなら幸いです。