Java : インタフェースの static メソッドの使いどころ
Java 8 から、インタフェースに static メソッドが定義できるようになりました。
本記事では、標準API の使われ方を参考に、どのようなケースで使うべきかをご紹介します。
概要
インタフェースの static メソッドは、インスタンスの生成なしで直接呼び出すことができます。
クラスの static メソッドと同じですね。
Java 8 で、言語仕様に追加されました。
public interface A {
static void func() {
System.out.println("static method : OK!");
}
}
A.func();
// 結果
// ↓
//static method : OK!
アクセス修飾子は、public と private が使えます。
省略すると public と同じになります。
public interface B {
// 省略した場合と同じです。
public static void func1() {
System.out.println("public : OK!");
func2();
}
private static void func2() {
System.out.println("private : OK!");
}
}
B.func1();
// 結果
// ↓
//public : OK!
//private : OK!
// コンパイルエラー (private なので直接は呼び出せません)
B.func2();
おすすめの使い方
それでは Java の 標準API を参考に、インタフェース static メソッドの使い方をみていきましょう。
標準API での使われ方
標準API では、ずばり…
- そのインタフェースのインスタンスを取得(生成) するためのメソッド
として使われています。
標準API の List インタフェースを例に見てみましょう。
List は、配列 の代わりとしてもよく使われる API ですね。
final var list = List.of("aaa", "bbb", "ccc");
System.out.println(list);
// 結果
// ↓
//[aaa, bbb, ccc]
List.of が static メソッドになります。
このメソッドは、パラメータで指定した要素を初期値としてもつインスタンスを返します。
( of メソッドで取得するインスタンスは 不変オブジェクト となります)
List 以外のコレクションでは、Set.of や Map.of なんかもあります。
他にも…
- InputStream.nullInputStream
- Path.of
- Function.identity
などなど。
いずれも自身のインスタンスを返す static メソッドがあります。
このように、標準API では自身のインスタンスを返すのに多く使われている印象ですね。
コード例
実際に、コード例でも見てみましょう。
電卓をイメージした Calculator インタフェースを作ってみます。
package aaa;
public interface Calculator {
void add(int value);
void getResult();
}
add メソッドで足し算をします。
そして、結果を result メソッドで取得します。
次に、Calculator の実装クラスとなる CalculatorImpl も作りましょう。
package aaa;
public class CalculatorImpl implements Calculator {
private int result;
@Override
public void add(int value) {
result += value;
}
@Override
public int getResult() {
return result;
}
}
Calculator と CalculatorImpl は aaa パッケージに属しています。
Calculator calc = new CalculatorImpl();
calc.add(10);
calc.add(20);
calc.add(50);
System.out.println(calc.getResult());
// 結果
// ↓
//80
使い方はこんな感じですね。
10 + 20 + 50 で、結果は 80 となります。
さて、しばらくして、異なるパッケージ(aaa パッケージ以外) に対して CalculatorImpl クラスは非公開にしたくなりました。 必要のないクラスやメソッドなどは、なるべく他に公開しない… 情報を隠蔽 する、というのはオブジェクト指向ではよくある考え方です。
そのためには、CalculatorImpl のアクセス修飾子である public を外して package private とします。
package private は、同一パッケージからのアクセスのみを許可します。
関連:アクセス修飾子の基本
public class CalculatorImpl implements Calculator {
...
}
↓
class CalculatorImpl implements Calculator {
...
}
これで、CalculatorImpl は別のパッケージからはアクセスできなくなりました。
package xxx;
...
// コンパイルエラー
Calculator calc = new CalculatorImpl();
aaa 以外のパッケージである xxx から CalculatorImpl を new しようとしてもエラーとなります。
期待どおりですね。
しかし、aaa 以外のパッケージで Calculator のインスタンスが生成できなくなりました。
これは困ったぞ…ということで、インスタンスを生成するためのメソッドが必要になります。
そこで使えるのが インタフェースの static メソッド です。
package aaa;
public interface Calculator {
static Calculator create() {
return new CalculatorImpl();
}
...
}
Calculator インタフェースに、static メソッドである create を作ります。
パッケージは aaa なので CalculatorImpl も new できます。
package xxx;
...
// インスタンス生成 OK!
Calculator calc = Calculator.create();
calc.add(10);
calc.add(20);
calc.add(50);
System.out.println(calc.getResult());
// 結果
// ↓
//80
Calculator.create メソッドは public です。
よって、xxx パッケージからもアクセスできます。
これで、CalculatorImpl は非公開にしつつ、異なるパッケージで Calculator のインスタンスを生成できました。
【ノート】
インタフェースの static メソッドを使わない手法もご紹介します。
それは、CalculatorFactory のような、インスタンスを生成するためのクラスを別に作ることです。
package aaa;
public class CalculatorFactory {
public static Calculator create() {
return new CalculatorImpl();
}
}
package xxx;
...
// インスタンス生成 OK!
Calculator calc = CalculatorFactory.create();
個人的には、これもありだと思います。
どちらがよいのかは難しいですね…
- Calculator.create のメリット
- インスタンス生成のための別のクラス(CalculatorFactoryなど) が不要
- CalculatorFactory.create のメリット
- インスタンス生成という役割を Calculator から分離できる。(つまり Calculator がシンプルになる)
補足
static メソッドは継承されない
上記の引用にもあるように、インタフェースの static メソッドは継承されません。
例で見てみましょう。
public interface A {
static void func1() {
System.out.println("A func1 : OK!");
}
static void func2() {
System.out.println("A func2 : OK!");
}
}
public interface X extends A {
static void func1() {
System.out.println("X func1 : OK!");
}
}
インタフェースの A と X に、同じ func1 メソッドがありますが、これはオーバーライドされません。
また、X には func2 は継承されません。
まずは A インタフェースとして func1, func2 メソッドを呼び出してみます。
A.func1();
A.func2();
// 結果
// ↓
//A func1 : OK!
//A func2 : OK!
どちらも A インタフェースの static メソッドが呼び出されました。
次に、A インタフェースを継承した X インタフェースから呼び出してみましょう。
X.func1();
// 結果
// ↓
//X func1 : OK!
// コンパイルエラー
X.func2();
func1 は X インタフェースの static メソッドが呼び出されました。
そして func2 は呼び出せずコンパイルエラーとなります。
このように、インタフェースの static メソッドは継承されません。
まとめ
標準API では、インタフェースの static メソッドは、
- そのインタフェースのインスタンスを取得(生成) するためのメソッド
としてよく使われています。
よって、自分でインタフェースの static メソッド を作るときも、標準API と同じように使うのが無難です。
ぜひ有効に活用していきたいですね。
関連記事
- if文の基本
- while文の基本
- for文の基本
- プリミティブ型 (基本データ型)
- リテラルの表記方法いろいろ
- クラスの必要最低限を学ぶ
- インタフェースの default メソッドとは
- var (型推論) のガイドライン
- アクセス修飾子の基本
- 配列 (Array) の使い方
- 拡張for文 (for-eachループ文)
- switch文ではなくswitch式を使おう
- try-with-resources文でリソースを自動的に解放
- テキストブロックの基本
- 列挙型 (enum) の基本
- ラムダ式の基本
- レコードクラスの基本 (Record Class)
- メソッド参照の基本
- シールクラスの基本(Sealed Class)
- 無名変数の使い方