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” を付けるだけです。これだけでこのメソッドはクラスの持ち物になります。
class StaticMethodSample1 { // staticを付ければstaticメソッドになる static void staticMethod() { } // staticを付けなければインスタンスメソッドになる void instanceMethod() { } }
最も大事なのは、staticメソッドの中ではインスタンスに属するモノは一切参照できませんし、実行もできないことです。具体的には、インスタンスフィールド・インスタンスメソッド・staticではない内部クラス・this/superは使えません。使うとコンパイルエラーになります。
staticメソッドを実行するクラス自身はインスタンスではないので、インスタンスフィールド・メソッドを持っていないからです。ですから、staticメソッドではstaticフィールドや他のstaticメソッド、引数を使ってプログラムを行います。
class StaticMethodSample2 { int instanceField = 100; // インスタンスフィールド static int staticField = 999; // staticフィールド void instanceMethod() { // インスタンスメソッド } static void otherStaticMethod() { // 他のstaticメソッド } class InstanceInnerClass { // インスタンスの内部クラス } static class StaticInnerClass { // staticな内部クラス } // staticメソッドの中で参照・実行できるものは何か? static void staticMethod(int argument) { System.out.println(staticField); // 〇:staticフィールドは使える System.out.println(argument); // 〇:引数もインスタンスメソッドと同様に使える otherStaticMethod(); // 〇:staticメソッドなら呼び出せる StaticInnerClass c1 = new StaticInnerClass(); // 〇:staticでない内部クラスはnewできる // 以下のいずれもコンパイルエラーとなる。インスタンスの持ち物に「直接」アクセスしているため System.out.println(instanceField); // ×:インスタンスフィールドは参照できない instanceMethod(); // ×:インスタンスメソッドは実行できない this.instanceMethod(); // ×:thisは参照できない super.toString(); // ×:superは参照できない InstanceInnerClass c2 = new InstanceInnerClass(); // ×:staticでない内部クラスは直接参照できない } }
なお、インスタンスの持ち物を直接参照・実行できないというだけであって、間接的になら大丈夫です。つまり、staticメソッドの中で自クラスのインスタンスを生成したり、引数として受け取るのなら普通に使えます。
その場合でも、あくまで個別のインスタンスが持つフィールド・メソッドにアクセスしているだけ…ということは忘れないようにしましょう。
class StaticMethodSample3 { int instanceField = 100; // インスタンスフィールド void instanceMethod() { // インスタンスメソッド } class InstanceInnerClass { // インスタンスの内部クラス } static void staticMethod(StaticMethodSample3 sample1) { StaticMethodSample3 sample2 = new StaticMethodSample3(); // staticメソッドの中でもインスタンスの生成はできる // 変数が指すインスタンスのフィールドへはアクセスできる System.out.println(sample1.instanceField); System.out.println(sample2.instanceField); // 変数が指すインスタンスのメソッドは呼び出せる sample1.instanceMethod(); sample2.instanceMethod(); // インスタンス内部クラスへは、それぞれのインスタンスを経由すればnewできる InstanceInnerClass c1 = sample1.new InstanceInnerClass(); InstanceInnerClass c2 = sample2.new InstanceInnerClass(); } }
3.staticメソッドの使い方
宣言したstaticメソッドを呼び出すには、原則として”クラス名.staticメソッド名”と指定します。前述したとおり、staticメソッドはクラスの持ち物ですから、クラス名を経由してメソッドを呼び出すのです。
ちなみに、staticメソッドを宣言したクラス内やサブクラス内で使うなら、クラス名部分の指定は不要です。インスタンスのコンテキストからも同じように呼び出せます。
class StaticMethodSample4 { static void staticMethod() { } static void staticMethod2() { staticMethod(); // 自クラス内からの呼び出しなので、クラス名は不要 StaticMethodSample4.staticMethod(); // これでも文法上は問題ない } void instanceMethod() { staticMethod(); // インスタンスメソッドからもクラス名なしで呼び出せる } } class StaticMethodSample5 { static void staticMethod() { StaticMethodSample4.staticMethod(); // 違うクラスから呼び出すときは、クラス名.staticメソッド名とする } void instanceMethod() { StaticMethodSample4.staticMethod(); // 違うクラスのインスタンスメソッドから呼び出すときも同じやり方 } } // 継承したクラスからもクラス名不要で呼び出せる class StaticMethodSample6 extends StaticMethodSample4 { static void staticMethod3() { staticMethod(); // StaticMethodSample4のstaticMethod() } void instanceMethod2() { staticMethod(); // 同じくStaticMethodSample4のstaticMethod() } }
なお、インスタンス(を指す変数)経由でもstaticメソッドは呼び出せます。しかし、クラスの持ち物を呼び出すのですからクラスを経由するのが自然ですし、普通です。
class StaticMethodSample7 { static void staticMethod() { } void instanceMethod() { this.staticMethod(); // this経由でも呼び出せる } public static void main(String[] args) { StaticMethodSample7 sample = new StaticMethodSample7(); sample.staticMethod(); // インスタンス(を指す変数)経由でも呼び出せる } }
しかも、開発環境によっては、インスタンス経由のstaticメソッド呼び出しは警告扱いになることがあります。例えば、Eclipseでは以下のような警告が出ることがあります。コンパイルエラーではないので動かす分には問題ありませんが、あまり良い書き方ではないということですね。
型 StaticMethodSample7 からの static メソッド staticMethod() には static にアクセスする必要があります
4.staticメソッドの注意事項
4-1.static・インスタンスメソッドのシグネチャは同じにできない
staticメソッドとインスタンスメソッドのシグネチャ(メソッド名、引数の型・並び順)は同じにはできません。すなわち、以下のメソッド宣言は行えず、コンパイルエラーとなります。
class StaticMethodSample8 { void method(int i) { } static void method(int x) { // インスタンスメソッドとstaticメソッドは同じシグネチャにはできない!! } }
4-2.staticメソッドのアクセス修飾子はインスタンスメソッドと同じ
private/protected/publicのアクセス修飾子の働きはインスタンスメソッドと同じです。以下のいずれも正しいものです。
class StaticMethodSample9 { // publicなstaticメソッド、全パッケージの全クラスから参照できる public static void publicStaticMethod() { } // protectedなstaticメソッド、同一パッケージまたは継承先クラスからのみ参照できる protected static void protectedStaticMethod() { } // package privateなstaticメソッド、同一パッケージからのみ参照できる static void packagePrivateStaticMethod() { } // privateなstaticメソッド、同一クラス内からのみ参照できる private static void privateStaticMethod() { } }
4-3.staticメソッドはabstractにはできない
staticメソッドは抽象メソッド(abstract)とはできません。後述しますが、staticメソッドはクラスの継承の対象外ですので、abstractとすることに意味がないからです。
abstract class StaticMethodSample10 { abstract static void staticMethod(); // コンパイルエラー!! }
4-4.【中級者向け】staticメソッドは継承されない
何回もお伝えしたとおり、staticメソッドはクラス固有の持ち物です。これは、staticメソッドはサブクラスへ継承されないということを意味します。
以下の例では、スーパークラス・サブクラスで同じシグネチャのstaticメソッドが存在します。インスタンスメソッドならサブクラスでオーバーライドしたものが使われますが、staticメソッドではどのクラスのものか明示しないなら、スーパークラスのstaticメソッドが呼ばれます。
class StaticMethodSample11 { static void staticMethod() { System.out.println("StaticMethodSample11.staticMethod()"); } void instanceMethod() { System.out.println("StaticMethodSample11.instanceMethod()"); } void invokeMethod() { // サブクラスから呼び出された場合は… staticMethod(); // このクラスのstaticMethodが呼び出される instanceMethod(); // サブクラスでオーバーライドしたinstanceMethodが呼び出される } } class StaticMethodSample12 extends StaticMethodSample11 { // スーパークラスにあるものと同じシグネチャのstaticメソッドを宣言 // このクラスのスコープでは、単純名としてのstaticMethod()はこれになる static void staticMethod() { System.out.println("StaticMethodSample12.staticMethod()"); } // インスタンスメソッドはオーバーライドしてみる void instanceMethod() { System.out.println("StaticMethodSample12.instanceMethod()"); } public static void main(String[] args) { new StaticMethodSample11().invokeMethod(); new StaticMethodSample12().invokeMethod(); staticMethod(); // このクラスで宣言されたstaticメソッドを呼び出すことになる StaticMethodSample11.staticMethod(); // スーパークラスの同名staticメソッドを呼びたいなら、クラスを経由する } }
// StaticMethodSample12の実行例 StaticMethodSample11.staticMethod() StaticMethodSample11.instanceMethod() StaticMethodSample11.staticMethod() ←スーパークラスのstaticMethodが呼び出されている StaticMethodSample12.instanceMethod() StaticMethodSample12.staticMethod() StaticMethodSample11.staticMethod()
このような動きになるのは、Javaの継承ではインスタンスメソッドはサブクラスでオーバーライドされますが、staticメソッドはされないためです。この動きからも、staticメソッドはクラスのものだということが分かるかと思います。
5.staticメソッドの活用方法
5-1.ユーティリティメソッドとしての活用
staticメソッドの最も普通の使い方は、インスタンスの状態に依存しない処理をさせることです。これはユーティリティメソッドとも呼ばれ、決まりきった処理などに用いられます。標準APIではStringやIntegerなどにも多くのstaticメソッドがありますし、CollectionsやArraysなどにあるものもよく使いますよね。
StringやIntegerなどでは、インスタンスメソッドとstaticメソッドが上手く使い分けされています。StringやIntegerはそれぞれのインスタンスが自分自身の文字列や数値を持つ一方で、StringやIntegerに関する共通的な処理もプログラムでは必要になります。
5-1-1.例:String.substring()とString.valueOf()
例えば、Stringのインスタンスメソッドとstaticメソッドには以下のものがあります。
public String substring(int beginIndex, int endIndex) public static String valueOf(int i)
substringはString自らが持つ文字列から切り出しますので、インスタンスメソッドがふさわしいです。SQLなどではsubstring(文字列, 開始位置, 切り出し文字数)などとしますが、JavaではString自身に「あなたが持っている文字列から指定位置を切り出したものをください」と頼むのですね。
一方、valueOfはintに対応するStringを得るメソッドです。この処理にはStringのインスタンスが持つ文字列は関係ありません。つまり、何かのStringのインスタンスに「このintに対するStringを生成してください」と頼んだとして、インスタンス自身が持つ文字列は結果に何も影響しないのです。
ですから、String.valueOf(int)はインスタンスではなくクラスの持ち物…すなわちstaticメソッドであるのがふさわしいのです。intに対するStringを生成するのは、インスタンスに関係のない決まりきった内容の処理だからです。
5-1-2.ユーティリティクラスだとprivateコンストラクタで明言する
なお、staticメソッドだけ持つクラスは、インスタンスとして生成されなくても機能を提供できます。それを徹底するため、コンストラクタをprivateとして外部からインスタンスを生成できないようにして、ユーティリティクラスであることを明確に示すことも普通です。
この例として、先述したCollectionsやArraysは、コンストラクタがprivateになっています。もしstaticメソッドばかりで、しかもprivateなコンストラクタのクラスを見かけたら、それはユーティリティクラスなのだと考えてください。
// Java 10のソースコードより抜粋 public class Arrays { : private Arrays() {} : public class Collections { : private Collections() { }
5-2.【中級者向け】staticファクトリーメソッドとしての活用
staticファクトリーメソッドとは、インスタンスを組み立てる処理を代行し、結果として組み立て済みのインスタンスを返すメソッドのことです。※デザインパターンでのFactory Methodとは内容が違いますのでご注意ください。
newするだけのインスタンス生成をわざわざstaticメソッドで?…と思われるかもしれません。ですが、実は様々なメリットがあります。この節でもいくつか紹介しますが、詳細を知りたい方は、例えば書籍「Effective Java」などに詳細が記載されていますので、ぜひ読んでみましょう!!
なお、Integer.valueOf(int)はstaticファクトリーメソッドの側面も持っています。内部の実装ではIntegerがキャッシュされていて、特定の範囲の数値に対しては、常にキャッシュされた同じIntegerのインスタンスが戻るのです。このように、Javaの内部でもstaticファクトリーメソッドは広く使われています。
5-2-1.インスタンス生成後の初期化処理をまとめて実行する
例として、普通はインスタンスを使う側がnewして必要な処理を呼び出しますが、その工程が複雑だとミスが起きますので、そこまでワンセットにして提供するパターンがあります。
// インスタンスを生成してから… SomeClass x = new SomeClass(); // 延々と初期化処理を行い… x.setA(); x.setB(); x.setC(); x.initialize(); // 必要な処理を呼び出し側で漏れなく全て実行し終わったら、ようやく使える状態になる x.someProcess();
// インスタンスを要求すれば、すぐ使える!! SomeClass x = SomeClass.build(); x.someProcess(); // SomeClassのファクトリーメソッド class SomeClass { // staticメソッド内で必要な初期化処理をすべて確実に行う static SomeClass build() { SomeClass ret = new SomeClass(); ret.setA(); ret.setB(); ret.setC(); ret.initialize(); return ret; } }
5-2-2.インスタンス生成処理に「意味」を付与する
応用として、意味や文脈をもって用途に応じたインスタンスを取得させるのにも使えます。例えば以下のようにすると、メソッド名で何を目的にしたインスタンスを戻すのかが明確になります。使う側がインスタンスをnewして必要な設定をするよりもずっと分かりやすいですよね。
それに、クラスにコンストラクタがたくさんある場合、それぞれのJavadocを読み込んでコンストラクタの意味を調べるよりも、メソッド名でどんな状態を持つインスタンスが戻ってくるか直感的にわかる方が、使う側にとってはより使いやすいと思いませんか?
class SomeClass { static SomeClass buildX() { // X用のインスタンスを組み立てて戻す } static SomeClass buildY() { // Y用のインスタンスを組み立てて戻す } static SomeClass buildZ() { // Z用のインスタンスを組み立てて戻す } }
5-2-3.サブクラスを意識させないインスタンス生成
さらに、上手にクラス設計ができていれば、固有の実装はサブクラスで行った上で、staticファクトリーメソッドを用いてインスタンスを生成するクラスを意識させないで済ませられます。
これだと、使う側はどのサブクラスを使うべきかを知らなくても、どのstaticメソッドを呼び出せばいいのか知っておくだけでいいのですね。
class SomeClass {} // デフォルト実装 class SomeClassX extends SomeClass {} // X用のサブクラス class SomeClassY extends SomeClass {} // Y用のサブクラス class SomeClassZ extends SomeClass {} // Z用のサブクラス class SomeClassBuilder { // 用途に応じて呼び出すstaticメソッドを変える→具体的なサブクラス名を知らなくてもいい static SomeClass buildX() { return new SomeClassX(); } static SomeClass buildY() { return new SomeClassY(); } static SomeClass buildZ() { return new SomeClassZ(); } // あるいは、条件に応じて生成するクラスをstaticメソッド内で選ぶ // 使う側はSomeClassとして使えれば、どのクラスのインスタンスであろうと関係ないはず static SomeClass build(欲しいインスタンスの条件を表す引数) { if (...) { return new SomeClassX(); } else if (...) { return new SomeClassY(); } else if (...) { return new SomeClassZ(); } else { return new SomeClass(); } } }
5-3.【中級者向け】Singletonパターンでの利用
staticファクトリーメソッドの応用の一つとして、あるクラスのインスタンスが1つだけしかないことを保証できます。その場合に良く使うのはデザインパターンのSingletonですが、これはstaticメソッドの活用事例としても有名なものです。
class StaticMethodSample { // 自分自身の唯一のインスタンスを持つstaticフィールド private static StaticMethodSample instance; // コンストラクタをprivateとして、外部からインスタンスを生成させない private StaticMethodSample() { } // 唯一のインスタンスが未初期化ならnewし、そうでなければstaticフィールドの値をreturn // ※マルチスレッドを考慮するなら、synchronizedメソッドとすべき static StaticMethodSample getInstance() { if (instance == null) { instance = new StaticMethodSample(); } return instance; } }
staticフィールドに自分のクラスのインスタンスを持ち、さらにコンストラクタはprivateとして外部からのインスタンス生成を不可能とします。インスタンスを取得するためのstaticメソッドを作り、その中だけでインスタンスを生成すれば、Singletonパターンの出来上がりです。
Singletonに限らず、フィールドに自分自身のクラスを持つ、というのが初心者には分かりづらいかもしれませんが、こういう書き方もOKなのです。
6.インターフェイスのstaticメソッド
Java 8から、インターフェイスにstaticメソッドを宣言できます。さらに、Java 9からはそれをprivateにもできます。ですから、そのインターフェイスに関連する処理をインターフェイス自身に宣言できるようになりました。defaultメソッドと併せて、使いようによってはとても便利ですね。
interface StaticMethodInterface { void instanceMethod(); // 普通の抽象メソッド(暗黙的にpublic abstractとなる) /* private void privateInstanceMethod(); // コンパイルエラー!! 抽象メソッドはprivateとはできない!! */ // クラスのstaticメソッドと同様に、インターフェイスにも宣言できる!! static void staticMethod() { System.out.println("static method in interface!!"); privateStaticMethod(); // インターフェイスのprivateなstaticメソッドを呼び出してみる } // staticメソッドはprivateにもできる!! (Java 9から) private static void privateStaticMethod() { System.out.println("private static method in interface!!"); } }
ですから、以下のように外部からも直接呼び出せます。インターフェイスを実装したクラスからも呼び出せますが、実装クラスからは直接呼び出せず、インターフェイス名を経由する必要があるのが玉に瑕です。
class StaticMethodSample13 { public static void main(String[] args) { StaticMethodInterface.staticMethod(); // インターフェイスのstaticメソッドを直接呼び出す /* StaticMethodInterface.privateStaticMethod(); // privateなものは呼び出せない */ } }
class StaticMethodSample14 implements StaticMethodInterface { @Override public void instanceMethod() { StaticMethodInterface.staticMethod(); // インターフェイスのstaticメソッドを呼び出している } }
これができるようになったのは、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メソッドはこんな風に使えるんだ、と感じていただけたなら幸いです。
コメント