1. はじめに
Java 8 以前、インターフェースは専ら「契約」でした。抽象メソッドのみで、実装もロジックもなく、約束だけ。ところが Java 8 以降、インターフェースは少し「自立」しました。default メソッドだけでなく、static メソッドも持てるようになったのです。
インターフェースの static メソッドは、実装(クラス)ではなくインターフェース自体に属するメソッドです。オブジェクトを作成せずに、インターフェース名から直接呼び出します。
たとえ話:
インターフェースの static メソッドは、オフィスの壁に貼られた手引きやメモのようなものです。各社員(クラス)はそれを利用できますが、そのメモ自体はどの社員にも個別には属していません。
インターフェースの static メソッドを使うと、そのインターフェースに関連する補助的な関数を、実装クラスの名前空間を汚さずにグルーピングできます。
インターフェースにおける static メソッドの構文
static メソッドは、インターフェース内でキーワード static を使って宣言します。実装(波括弧内のコード)を持ち、呼び出しはインターフェース名経由でのみ可能です。
例:
public interface MathUtils {
static int sum(int a, int b) {
return a + b;
}
static double average(int a, int b) {
return (a + b) / 2.0;
}
}
インターフェースの static メソッドの呼び出し:
int result = MathUtils.sum(5, 7); // 12
double avg = MathUtils.average(10, 20); // 15.0
注意:
インターフェースの static メソッドを、実装クラスやそのオブジェクト経由で呼ぶことはできません。必ずインターフェース名から呼び出します。
2. インターフェースの static メソッドは default メソッドとどう違う?
static メソッド:
- インターフェース自体に属する。
- 実装クラスには継承されない。
- クラスのオブジェクト経由では呼び出せない。
- 実装クラスでオーバーライドできない。
- インターフェース名からのみ呼び出せる。
default メソッド:
- インターフェースを実装するクラスのオブジェクト(インスタンス)に属する。
- 実装クラスでオーバーライドできる。
- 実装クラスのオブジェクト経由で呼び出せる。
- 実装クラスに継承される。
まとめ: default メソッドはオブジェクトの機能を拡張し、static メソッドはインターフェース自体の機能を拡張します。
比較例:
interface Printer {
default void print(String text) {
System.out.println("Default: " + text);
}
static void info() {
System.out.println("Printer interface v1.0");
}
}
class ConsolePrinter implements Printer {}
public class Main {
public static void main(String[] args) {
Printer.info(); // インターフェース経由で static メソッドを呼び出す
ConsolePrinter cp = new ConsolePrinter();
cp.print("Hello!"); // オブジェクト経由で default メソッドを呼び出す
// cp.info(); // エラー! static メソッドはオブジェクトからは呼べない
}
}
3. なぜインターフェースに static メソッドが必要なのか?
インターフェースの static メソッドが導入される前は、インターフェースに関連するユーティリティ関数を追加したい場合、Utils や Helper のサフィックスを付けた別クラスを作成する必要がありました:
public interface Movable {
void move(int x, int y);
}
public class MovableUtils {
public static void resetPosition(Movable m) {
m.move(0, 0);
}
}
今ではこれをインターフェース内で直接行えます:
public interface Movable {
void move(int x, int y);
static void resetPosition(Movable m) {
m.move(0, 0);
}
}
これにより、コードはより論理的で結び付きが強くなります。インターフェースに関係するメソッドは、いまやその中に直接置けます。
利点:
- ユーティリティ関数をインターフェースの契約の近くにまとめられる。
- 実装クラスの名前空間を「汚さない」。
- 可読性と保守性が向上する。
4. インターフェースの static メソッドの制約と特徴
インターフェースの static メソッドは、実装クラスに継承されません。
実装クラスのオブジェクトやクラス名からは呼び出せません。インターフェース名からのみ呼び出せます。
インターフェースの static メソッドは常に実装を持ち、abstract や default にはできません。
常に実装を持ちます。
static メソッドは、インターフェースの非 static メソッドや変数にアクセスできません。
アクセスできるのは、他の static メンバー(たとえば static final 定数)だけです。
インターフェースの static メソッドは private にもできる(Java 9+)。
インターフェース内部で使用する補助的な private static メソッドを作成できます。
5. 例: Movable インターフェースの static メソッド
インターフェース Movable に static メソッドを追加してみましょう。Movable をロボット、動物、乗り物などさまざまなクラスが実装しているとします。
ステップ 1. static メソッド付きでインターフェースを宣言:
public interface Movable {
void move(int x, int y);
static void resetPosition(Movable obj) {
obj.move(0, 0);
}
static double distance(int x1, int y1, int x2, int y2) {
int dx = x2 - x1;
int dy = y2 - y1;
return Math.sqrt(dx * dx + dy * dy);
}
}
ステップ 2. クラスでインターフェースを実装:
public class Robot implements Movable {
private int x, y;
public Robot(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public void move(int x, int y) {
System.out.println("ロボットは座標 (" + x + "," + y + ") に移動します");
this.x = x;
this.y = y;
}
public void printPosition() {
System.out.println("現在位置: (" + x + "," + y + ")");
}
}
ステップ 3. インターフェースの static メソッドを使う:
public class Main {
public static void main(String[] args) {
Robot robby = new Robot(10, 15);
robby.printPosition();
// インターフェースの static メソッドで位置をリセット
Movable.resetPosition(robby);
robby.printPosition();
// インターフェースの static メソッドで2点間の距離を計算
double dist = Movable.distance(0, 0, 10, 15);
System.out.println("距離: " + dist);
}
}
結果:
現在位置: (10,15)
ロボットは座標 (0,0) に移動します
現在位置: (0,0)
距離: 18.027756377319946
注意:
呼び出しているのは Movable.resetPosition(robby) であって、robby.resetPosition() ではありません。static メソッドは、特定のオブジェクトではなくインターフェースに論理的に属する操作に便利です。
6. インターフェースの private static メソッド
ときにはインターフェース内で、内部だけで使う補助メソッドが必要になります(例えば、複数の static や default メソッドでコード重複を避けるため)。Java 9 以降、インターフェースは private static メソッドをサポートします。
例:
public interface Logger {
static void logInfo(String message) {
log("INFO", message);
}
static void logError(String message) {
log("ERROR", message);
}
private static void log(String level, String message) {
System.out.println("[" + level + "] " + message);
}
}
これで log() はインターフェースの外部からは利用できませんが、他の static メソッド内部で使われます。
7. Java 標準ライブラリではどこに static メソッドがある?
インターフェースの static メソッドは、Java の標準ライブラリでも積極的に使われています。特にコレクションや関数型インターフェースで顕著です。
例:
- Comparator.comparing()、Comparator.reverseOrder() — Comparator インターフェースの static メソッド。
- Predicate.isEqual() — Predicate インターフェースの static メソッド。
- List.of()、Set.of()、Map.of()(Java 9+)— 不変コレクションを作成するための static メソッド。
Comparator の例:
import java.util.Comparator;
public class Main {
public static void main(String[] args) {
Comparator<String> cmp = Comparator.reverseOrder();
int res = cmp.compare("a", "b"); // 正の数。逆順では "a" は "b" より大きいため
System.out.println(res);
}
}
8. インターフェースの static メソッドでよくあるミス
エラー1: 実装クラスのオブジェクトから static メソッドを呼び出そうとする。
これは動作しません! インターフェースの static メソッドはインターフェース名からのみ呼び出します。例えば Movable.resetPosition(obj) であって、obj.resetPosition() ではありません。
エラー2: 実装クラスでインターフェースの static メソッドをオーバーライドしようとする。
static メソッドは継承もオーバーライドもされません! クラス側に同名の static メソッドを宣言しても、それはインターフェースとは無関係な別メソッドです。
エラー3: static メソッドが非 static メンバーにアクセスできないことを忘れる。
インターフェースの static メソッドが使えるのは static メンバー(例えば static final 定数)のみで、非 static のメソッドや変数にはアクセスできません。
エラー4: default メソッドとの混同。
default はオブジェクト経由、static はインターフェース名経由のみ。取り違えないようにしましょう!
GO TO FULL VERSION