1. 概要

Java 8は、ラムダ式関数インターフェースメソッド参照ストリームなどのいくつかの新しい機能をテーブルにもたらしました。 ]オプション、およびstaticおよびdefaultメソッドのインターフェース。

これらの機能のいくつかについては、別の記事ですでに説明しています。 それにもかかわらず、インターフェースのstaticおよびdefaultメソッドは、それ自体でより深く調べる価値があります。

このチュートリアルでは、インターフェイスで静的メソッドとデフォルトメソッドを使用する方法を学習し、それらが役立つ可能性のあるいくつかの状況について説明します。

2. インターフェイスにデフォルトのメソッドが必要な理由

通常のインターフェースメソッドと同様に、デフォルトメソッドは暗黙的にパブリックです。 public修飾子を指定する必要はありません。

通常のインターフェースメソッドとは異なり、メソッドシグネチャの先頭にデフォルトのキーワードを使用して宣言し、実装を提供します。

簡単な例を見てみましょう。

public interface MyInterface {
    
    // regular interface methods
    
    default void defaultMethod() {
        // default method implementation
    }
}

Java8リリースにdefaultメソッドが含まれている理由は明らかです。

インターフェイスに1つまたは複数の実装がある抽象化に基づく一般的な設計では、1つ以上のメソッドがインターフェイスに追加されると、すべての実装もそれらを実装するように強制されます。 そうしないと、デザインが壊れてしまいます。

デフォルトのインターフェースメソッドは、この問題に対処するための効率的な方法です。 それらを使用すると、実装で自動的に使用できる新しいメソッドをインターフェイスに追加できます。 したがって、実装クラスを変更する必要はありません。

このようにして、下位互換性は、実装者をリファクタリングすることなく、きちんと維持されます

3. 動作中のデフォルトのインターフェースメソッド

default インターフェースメソッドの機能をよりよく理解するために、簡単な例を作成しましょう。

ナイーブなVehicleインターフェースと1つの実装だけがあるとします。 もっとあるかもしれませんが、それを単純に保ちましょう:

public interface Vehicle {
    
    String getBrand();
    
    String speedUp();
    
    String slowDown();
    
    default String turnAlarmOn() {
        return "Turning the vehicle alarm on.";
    }
    
    default String turnAlarmOff() {
        return "Turning the vehicle alarm off.";
    }
}

それでは、実装クラスを書いてみましょう。

public class Car implements Vehicle {

    private String brand;
    
    // constructors/getters
    
    @Override
    public String getBrand() {
        return brand;
    }
    
    @Override
    public String speedUp() {
        return "The car is speeding up.";
    }
    
    @Override
    public String slowDown() {
        return "The car is slowing down.";
    }
}

最後に、典型的な main クラスを定義しましょう。このクラスは、 Car のインスタンスを作成し、そのメソッドを呼び出します。

public static void main(String[] args) { 
    Vehicle car = new Car("BMW");
    System.out.println(car.getBrand());
    System.out.println(car.speedUp());
    System.out.println(car.slowDown());
    System.out.println(car.turnAlarmOn());
    System.out.println(car.turnAlarmOff());
}

default メソッド、 turnAlarmOn()および turnAlarmOff()、Vehicleインターフェースから自動的に利用可能であることに注意してください。車のクラス

さらに、ある時点で defaultメソッドをVehicle インターフェースに追加することにした場合でも、アプリケーションは引き続き機能し、クラスに実装を提供させる必要はありません。新しいメソッドのために。

インターフェイスのデフォルトメソッドの最も一般的な使用法は、実装クラスを分解することなく、特定のタイプに追加機能を段階的に提供することです。

さらに、それらを使用して、既存の抽象メソッドの周りに追加機能を提供できます。

public interface Vehicle {
    
    // additional interface methods 
    
    double getSpeed();
    
    default double getSpeedInKMH(double speed) {
       // conversion      
    }
}

4. 複数のインターフェース継承ルール

デフォルトのインターフェースメソッドは非常に優れた機能ですが、言及する価値のあるいくつかの注意点があります。 Javaではクラスが複数のインターフェースを実装できるため、クラスが同じデフォルトメソッドを定義する複数のインターフェースを実装するとどうなるかを知ることが重要です

このシナリオをよりよく理解するために、新しいアラームインターフェイスを定義し、Carクラスをリファクタリングしましょう。

public interface Alarm {

    default String turnAlarmOn() {
        return "Turning the alarm on.";
    }
    
    default String turnAlarmOff() {
        return "Turning the alarm off.";
    }
}

この新しいインターフェースが独自のdefaultメソッドのセットを定義すると、CarクラスはVehicleAlarmの両方を実装します。

public class Car implements Vehicle, Alarm {
    // ...
}

この場合、コードはコンパイルされません。これは、複数のインターフェイスの継承(別名菱形継承)によって競合が発生するためです。 Car クラスは、defaultメソッドの両方のセットを継承します。 では、どれを呼び出す必要がありますか?

このあいまいさを解決するには、メソッドの実装を明示的に提供する必要があります。

@Override
public String turnAlarmOn() {
    // custom implementation
}
    
@Override
public String turnAlarmOff() {
    // custom implementation
}

クラスにインターフェースの1つのデフォルトメソッドを使用させることもできます。

Vehicleインターフェースからdefaultメソッドを使用する例を見てみましょう。

@Override
public String turnAlarmOn() {
    return Vehicle.super.turnAlarmOn();
}

@Override
public String turnAlarmOff() {
    return Vehicle.super.turnAlarmOff();
}

同様に、Alarmインターフェイス内で定義されたdefaultメソッドをクラスに使用させることができます。

@Override
public String turnAlarmOn() {
    return Alarm.super.turnAlarmOn();
}

@Override
public String turnAlarmOff() {
    return Alarm.super.turnAlarmOff();
}

Carクラスにデフォルトメソッドの両方のセットを使用させることも可能です

@Override
public String turnAlarmOn() {
    return Vehicle.super.turnAlarmOn() + " " + Alarm.super.turnAlarmOn();
}
    
@Override
public String turnAlarmOff() {
    return Vehicle.super.turnAlarmOff() + " " + Alarm.super.turnAlarmOff();
}

5. 静的インターフェースメソッド

インターフェイスでdefaultメソッドを宣言することに加えて、 Java 8では、インターフェイスで静的メソッドを定義および実装することもできます。

static メソッドは特定のオブジェクトに属していないため、インターフェイスを実装するクラスのAPIの一部ではありません。 したがって、メソッド名の前にあるインターフェイス名を使用して呼び出す必要があります。

static メソッドがインターフェイスでどのように機能するかを理解するために、 Vehicle インターフェイスをリファクタリングし、staticユーティリティメソッドを追加してみましょう。

public interface Vehicle {
    
    // regular / default interface methods
    
    static int getHorsePower(int rpm, int torque) {
        return (rpm * torque) / 5252;
    }
}

インターフェイス内で静的メソッドを定義することは、クラスで静的メソッドを定義することと同じです。さらに、 static メソッドは、他のstaticおよびdefault内で呼び出すことができます。 メソッド。

特定の車両のエンジンの馬力を計算するとします。 getHorsePower()メソッドを呼び出すだけです。

Vehicle.getHorsePower(2500, 480));

static インターフェースメソッドの背後にある考え方は、関連するメソッドを1つの場所にまとめることで、デザインの凝集度を向上させるシンプルなメカニズムを提供することです。物体。

同じことが抽象クラスでもほぼ可能です。主な違いは、抽象クラスがコンストラクター、状態、および動作を持つことができることです。

さらに、インターフェイスの静的メソッドを使用すると、静的メソッドの単なるプレースホルダーである人工的なユーティリティクラスを作成しなくても、関連するユーティリティメソッドをグループ化できます。

6. 結論

この記事では、Java8でのstaticおよびdefaultインターフェースメソッドの使用について詳しく説明しました。 一見すると、この機能は、特にオブジェクト指向の純粋主義者の観点からは、少しずさんなように見えるかもしれません。 理想的には、インターフェースは動作をカプセル化するべきではなく、特定のタイプのパブリックAPIを定義するためにのみ使用する必要があります。

ただし、既存のコードとの下位互換性を維持する場合は、staticメソッドとdefaultメソッドが適切なトレードオフになります。

いつものように、この記事に示されているすべてのコードサンプルは、GitHubから入手できます。