Java 8のインターフェースの変更には、インターフェース内での静的メソッドとデフォルトメソッドが含まれています。Java 8以前では、インターフェース内でメソッドの宣言のみを持つことができました。しかし、Java 8からは、インターフェース内でデフォルトメソッドと静的メソッドを持つことができます。
Java 8のインターフェース
インターフェースの設計は常に難しい仕事でした。なぜなら、インターフェースに追加のメソッドを追加する場合、すべての実装クラスを変更する必要があるからです。インターフェースが古くなるにつれて、それを実装するクラスの数は増え、インターフェースを拡張することができなくなる可能性があります。そのため、アプリケーションを設計する際には、ほとんどのフレームワークがベースの実装クラスを提供し、それを拡張してアプリケーションに適用可能なメソッドをオーバーライドします。では、Java 8のインターフェースの変更におけるデフォルトインターフェースメソッドと静的インターフェースメソッド、およびその導入の理由について見てみましょう。
Javaインターフェースのデフォルトメソッド
デフォルトメソッドをJavaインターフェースに作成するには、メソッドのシグネチャと一緒に “default” キーワードを使用する必要があります。例えば、
package com.journaldev.java8.defaultmethod;
public interface Interface1 {
void method1(String str);
default void log(String str){
System.out.println("I1 logging::"+str);
}
}
`log(String str)` が Interface1
のデフォルトメソッドであることに注意してください。これにより、クラスがInterface1を実装すると、インターフェースのデフォルトメソッドの実装を提供する必要はありません。この機能により、追加のメソッドを持つインターフェースを拡張することができ、必要なのはデフォルトの実装を提供するだけです。以下のメソッドを持つ別のインターフェースがあるとします:
package com.journaldev.java8.defaultmethod;
public interface Interface2 {
void method2();
default void log(String str){
System.out.println("I2 logging::"+str);
}
}
Javaでは、複数のクラスを拡張することは許可されていません。これは “ダイヤモンドの問題” につながり、コンパイラがどのスーパークラスのメソッドを使用するかを決定できなくなるからです。デフォルトメソッドがある場合、このダイヤモンドの問題はインターフェースでも発生します。なぜなら、クラスが Interface1
と Interface2
の両方を実装し、共通のデフォルトメソッドを実装しない場合、コンパイラがどちらを選択するか決定できなくなるからです。複数のインターフェースを拡張することはJavaの重要な部分であり、これはコアJavaクラスだけでなく、ほとんどのエンタープライズアプリケーションやフレームワークでも見つけることができます。したがって、この問題がインターフェースで発生しないようにするために、共通のデフォルトメソッドの実装を提供することが義務付けられています。したがって、クラスが上記の両方のインターフェースを実装している場合、コンパイラはコンパイル時エラーをスローする前に、`log()` メソッドの実装を提供する必要があります。両方の Interface1
と Interface2
を実装しているシンプルなクラスは次のようになります:
package com.journaldev.java8.defaultmethod;
public class MyClass implements Interface1, Interface2 {
@Override
public void method2() {
}
@Override
public void method1(String str) {
}
@Override
public void log(String str){
System.out.println("MyClass logging::"+str);
Interface1.print("abc");
}
}
Javaインターフェースのデフォルトメソッドに関する重要なポイント:
- Javaのインターフェースデフォルトメソッドは、実装クラスを壊す心配なしにインターフェースを拡張するのに役立ちます。
- Javaのインターフェースデフォルトメソッドは、インターフェースと抽象クラスの違いを取り除きました。
- Java 8のインターフェースデフォルトメソッドは、ユーティリティクラスを回避するのに役立ちます。たとえば、すべてのCollectionsクラスメソッドをインターフェース自体で提供できます。
- Javaのインターフェースデフォルトメソッドは、ベース実装クラスを削除するのに役立ちます。デフォルトの実装を提供し、実装クラスはどちらをオーバーライドするかを選択できます。
- インターフェースにデフォルトメソッドを導入する主な理由の1つは、Java 8のCollections APIを拡張してラムダ式をサポートすることです。
- 階層内のクラスに同じシグネチャのメソッドがある場合、デフォルトメソッドは無関係になります。デフォルトメソッドはjava.lang.Objectからのメソッドをオーバーライドできません。その理由は非常に単純です。ObjectはすべてのJavaクラスの基本クラスだからです。そのため、インターフェースでObjectクラスメソッドをデフォルトメソッドとして定義しても無駄です。Objectクラスメソッドが常に使用されるためです。混乱を避けるために、Objectクラスメソッドをオーバーライドするデフォルトメソッドを持つことはできません。
- Javaのインターフェースデフォルトメソッドは、Defenderメソッドまたは仮想拡張メソッドとも呼ばれます。
Java Interface Static Method
Javaのインターフェースの静的メソッドは、デフォルトメソッドと似ていますが、実装クラスでオーバーライドできない点が異なります。この特徴は、実装クラスでの不適切な実装による望ましくない結果を避けるのに役立ちます。簡単な例でこれを見てみましょう。
package com.journaldev.java8.staticmethod;
public interface MyData {
default void print(String str) {
if (!isNull(str))
System.out.println("MyData Print::" + str);
}
static boolean isNull(String str) {
System.out.println("Interface Null Check");
return str == null ? true : "".equals(str) ? true : false;
}
}
これに対応するisNull()メソッドを持つ実装クラスを見てみましょう。
package com.journaldev.java8.staticmethod;
public class MyDataImpl implements MyData {
public boolean isNull(String str) {
System.out.println("Impl Null Check");
return str == null ? true : false;
}
public static void main(String args[]){
MyDataImpl obj = new MyDataImpl();
obj.print("");
obj.isNull("abc");
}
}
isNull(String str)
は単純なクラスメソッドであり、インターフェースメソッドをオーバーライドしていません。たとえば、isNull()メソッドに@Overrideの注釈を追加すると、コンパイラエラーが発生します。アプリケーションを実行すると、以下の出力が得られます。
Interface Null Check
Impl Null Check
もし、インターフェースメソッドを静的からデフォルトに変更すると、以下の出力が得られます。
Impl Null Check
MyData Print::
Impl Null Check
Javaのインターフェースの静的メソッドは、インターフェースメソッドにだけ表示され、MyDataImpl
クラスからisNull()メソッドを削除すると、MyDataImpl
オブジェクトで使用できなくなります。ただし、他の静的メソッドと同様に、クラス名を使用してインターフェースの静的メソッドを使用できます。たとえば、有効な文は次のとおりです:
boolean result = MyData.isNull("abc");
Javaのインターフェースの静的メソッドに関する重要なポイント:
- Javaのインターフェースの静的メソッドは、インターフェースの一部であり、実装クラスのオブジェクトで使用できません。
- Javaのインターフェースの静的メソッドは、ユーティリティメソッド(nullチェック、コレクションのソートなど)を提供するために役立ちます。
- Javaのインターフェースの静的メソッドは、実装クラスがそれらをオーバーライドできないようにすることでセキュリティを提供します。
- Objectクラスのメソッドに対してはインターフェースの静的メソッドを定義できません。これにより、コンパイラエラーが発生します。「この静的メソッドはObjectのインスタンスメソッドを隠すことはできません」と表示されます。
- Javaではこれが許可されていないためです。Objectはすべてのクラスの基本クラスであり、同じシグネチャを持つクラスレベルの静的メソッドと別のインスタンスメソッドを持つことはできません。
Javaの関数型インターフェース
投稿をまとめる前に、Functionalインタフェースについて簡単に紹介したいと思います。正確に1つの抽象メソッドを持つインタフェースは、Functionalインタフェースとして知られています。新しいアノテーション@FunctionalInterfaceが導入され、インタフェースをFunctionalインタフェースとしてマークするために使用されます。@FunctionalInterfaceアノテーションは、Functionalインタフェース内に誤って抽象メソッドが追加されることを防ぐための機能です。オプションですが、使用することが良い慣例です。Functionalインタフェースは、Java 8の待望の機能であり、ラムダ式を使用してインスタンス化することが可能になります。新しいjava.util.function
パッケージには、ラムダ式とメソッド参照の対象タイプを提供するために、多くのFunctionalインタフェースが追加されています。将来の投稿では、Functionalインタフェースとラムダ式について詳しく見ていきます。