CodeGym /コース /JAVA 25 SELF /インターフェースの static メソッド

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

JAVA 25 SELF
レベル 21 , レッスン 3
使用可能

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 メソッドが導入される前は、インターフェースに関連するユーティリティ関数を追加したい場合、UtilsHelper のサフィックスを付けた別クラスを作成する必要がありました:

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 はインターフェース名経由のみ。取り違えないようにしましょう!

コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION