広告

Java : インタフェースの static メソッドの使いどころ

Java 8 から、インタフェースに static メソッドが定義できるようになりました。
本記事では、標準API の使われ方を参考に、どのようなケースで使うべきかをご紹介します。


概要

An interface can declare static methods, which are invoked without reference to a particular object.

インタフェースの 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.ofMap.of なんかもあります。

他にも…

などなど。
いずれも自身のインスタンスを返す 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 メソッドは継承されない

An interface does not inherit private or static methods from its superinterfaces.

上記の引用にもあるように、インタフェースの 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 と同じように使うのが無難です。
ぜひ有効に活用していきたいですね。


関連記事

ページの先頭へ